Obraz zawierający tekst, Czcionka, Grafika

Opis wygenerowany automatycznie 

Kierunek Informatyka

 

Instrukcja do ćwiczeń laboratoryjnych nr:

9

Nazwa przedmiotu:
Programowanie aplikacji mobilnych

Temat: Tryb graficzny w systemie android, grafika, animacje   

Tryb studiów: stacjonarne

Czas trwanie ćw.

2x45 min

Autor materiałów: dr Marcin Skuba

 

1. Treści programowe:

Tryb graficzny, rysowanie figur, wyświetlanie obrazków pochodzących z pliku, kolory, konfiguracja pióra, płótno, obsługa zdarzeń związanych z dotykowym ekranem, skalowanie.

 

2. Cel zajęć:

Celem zajęć jest zapoznanie studentów z mechanizmami grafiki dwuwymiarowej oraz obsługa zdarzeń związanych z ekranem. Ten sposób programowania może być wykorzystany do tworzenia prostych gier lub aplikacji w trybie graficznym.

 

3. Materiały dydaktyczne

 

Przykład przedstawiający rysowanie własnych kształtów na widoku przypiętym do aktywności:

 

Obraz zawierający zrzut ekranu, tekst, diagram, Grafika

Zawartość wygenerowana przez AI może być niepoprawna.

 

Metoda onDraw(Canvas canvas) jest podstawowym elementem do tworzenia niestandardowych elementów graficznych w Androidzie. Jest to funkcja wywoływana przez system, gdy widok (lub jego część) musi zostać ponownie narysowany. Główną ideą jest to, że musisz nadpisać tę metodę w swojej niestandardowej klasie dziedziczącej po View, aby zdefiniować, jak widok ma wyglądać. Metoda ta otrzymuje obiekt Canvas, który działa jak płótno malarskie, oraz obiekt Paint, który jest jak narzędzie malarskie (określa kolor, styl, grubość linii itp.). Wszystkie niestandardowe kształty – takie jak linie, okręgi, prostokąty, łuki czy złożone ścieżki – są rysowane przez wywołanie odpowiednich metod na obiekcie Canvas, zdefiniowanych przez styl i atrybuty obiektu Paint. Cały proces rysowania odbywa się w wątku głównym UI, dlatego kod w onDraw musi być szybki, aby nie blokować interfejsu.

 

Plik MainActivity.java

 

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {

   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
     
//setContentView(R.layout.activity_main);
       
setContentView(new Grafika(this, null));

    }
}

 

Klasa widoku Grafika.java dziedziczy z klawy View:

 

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.NonNull;

public class Grafika extends View {
    Context
context;
   
private static final String TAG = "TouchHandler";
   
int x,y;
   
public Grafika(Context context, AttributeSet attrs) {
       
super(context, attrs);
       
this.context = context;
    }

   
@Override
   
protected void onDraw(@NonNull Canvas canvas) {
       
super.onDraw(canvas);

       
// --- 1. Ustawienia początkowe ---
       
canvas.drawRGB(99, 99, 99); // Tło: szare (RGB)
       
Paint paint = new Paint();

       
// --- 2. Tekst i Linia (Zielony) ---
       
paint.setColor(Color.GREEN);
        paint.setTextSize(
80);
        canvas.drawText(
"Tryb graficzny Android", 10, 190, paint);

        canvas.drawLine(
0, 300, getWidth(), 300, paint);

        paint.setStrokeWidth(
100);
        paint.setStyle(Paint.Style.FILL);
// Wypełnienie
       
canvas.drawCircle(200, 420, 100, paint);

       
// --- 3. Prostokąt z Zasobów (Użycie Aktualnego API) ---

        // Aktualny i bezpieczny sposób pobierania koloru
       
int color = context.getColor(R.color.colorPrimary);
        paint.setColor(color);

       
// Prawidłowe pobieranie wymiarów z pliku zasobów dimen.XML
       
float szerokosc = context.getResources().getDimension(R.dimen.szerokosc);
       
float wysokosc = context.getResources().getDimension(R.dimen.wysokosc);
       
int x = getWidth() / 2;
       
int y = getHeight() / 2;

       
// Rysowanie prostokąta wypełnionego (używa koloru z XML)
       
canvas.drawRect(x, y, x + szerokosc, y + wysokosc, paint);

       
// --- 4. Elipsa i Prostokąt F (Niebieski/Zielony) ---

       
RectF shape = new RectF(100, 600, 300, 800);
        paint.setColor(Color.BLUE);
        canvas.drawRect(shape, paint);
// Prostokąt wypełniony

       
paint.setColor(Color.GREEN);
        canvas.drawOval(shape, paint);
// Elipsa wypełniona (nadpisuje prostokąt)

        // --- 5. NOWY OBIEKT: Zaokrąglony Prostokąt (drawRoundRect) ---

        // Zmiana na czerwony kolor
       
paint.setColor(Color.RED);
       
// Wymiary zaokrąglonego prostokąta
       
RectF roundRect = new RectF(50, 850, 450, 1050);
       
float radius = 40f; // Promień zaokrąglenia

       
canvas.drawRoundRect(roundRect, radius, radius, paint);

       
// --- 6. NOWY OBIEKT: Łuk (drawArc) ---

       
paint.setColor(Color.YELLOW);
       
// Obszar wpisania łuku
       
RectF arcRect = new RectF(500, 850, 900, 1050);
       
float startAngle = 90f; // Kąt początkowy (na dole)
       
float sweepAngle = 180f; // Zakres kąta (półkole)
       
boolean useCenter = true; // Czy łuk ma być połączony ze środkiem (jak wycinek tortu)

       
canvas.drawArc(arcRect, startAngle, sweepAngle, useCenter, paint);

       
// --- 7. NOWY OBIEKT: Ścieżka (drawPath) - Niestandardowy Trójkąt (tylko kontur) ---

       
paint.setColor(Color.CYAN);
        paint.setStyle(Paint.Style.STROKE);
// Ustawienie RYSOWANIA KONTURU
       
paint.setStrokeWidth(15);

        Path trianglePath =
new Path();
        trianglePath.moveTo(
500, 1200); // Punkt startowy
       
trianglePath.lineTo(800, 1300); // Linia do punktu 2
       
trianglePath.lineTo(500, 1400); // Linia do punktu 3
       
trianglePath.close(); // Zamknięcie ścieżki (powrót do punktu startowego)

       
canvas.drawPath(trianglePath, paint);
    }
}

 

Plik zasobów z wymiarami dimen.xml

 

Obraz zawierający tekst, zrzut ekranu, Czcionka

Zawartość wygenerowana przez AI może być niepoprawna.

 

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen
name="szerokosc">50dp</dimen>
    <dimen
name="wysokosc">50dp</dimen>
</resources>

 

Plik zasobów z wymiarami colors.xml

 

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color
name="colorPrimary">#008577</color>
    <color
name="colorPrimaryDark">#00574B</color>
    <color
name="colorAccent">#D81B60</color>
    <color
name="color">#ff00ff</color>
</resources>

 

 

Obsługa zdarzeń związanych z obsługą ekranu dotykowego:

 

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.NonNull;

public class Grafika extends View {
    Context
context;
   
private static final String TAG = "TouchHandler";
   
int x=100,y=150;


   
public Grafika(Context context, AttributeSet attrs) {
       
super(context, attrs);
       
this.context = context;
    }

   
@Override
   
protected void onDraw(@NonNull Canvas canvas) {
       
super.onDraw(canvas);

        canvas.drawRGB(
99, 99, 99); // Tło: szary (RGB)
       
Paint paint = new Paint();

        paint.setStrokeWidth(
100);
        paint.setColor(Color.BLUE);
        canvas.drawCircle(
x, y, 60, paint);
    }

   
@Override
   
public boolean onTouchEvent(MotionEvent event) {
       
// Pobranie współrzędnych dotyku
       
this.x=(int)event.getX();
       
this.y=(int)event.getY();

        invalidate();
// Odświeżenie widoku po wykonaniu akcji
        // Kluczowa konstrukcja switch do identyfikacji akcji
       
switch (event.getAction()) {

           
case MotionEvent.ACTION_DOWN:
               
// 1. Zdarzenie POJEDYNCZEGO dotknięcia (pierwszy palec)
               
Log.d(TAG, "ACTION_DOWN: Dotknięcie ekranu. X=" + x + ", Y=" + y);
               
// Należy zwrócić true, aby system wiedział, że interesuje nas to zdarzenie
               
return true;

           
case MotionEvent.ACTION_MOVE:
               
// 2. Zdarzenie PRZESUNIĘCIA (palec przesuwa się po ekranie)
               
Log.d(TAG, "ACTION_MOVE: Przesunięcie. X=" + x + ", Y=" + y);
               
break;

           
case MotionEvent.ACTION_UP:
               
// 3. Zdarzenie ZWOLNIENIA dotyku (palec został podniesiony)
               
Log.d(TAG, "ACTION_UP: Zwolnienie dotyku.");
               
break;

           
case MotionEvent.ACTION_CANCEL:
               
// 4. Zdarzenie ANULOWANIA (np. system przejął kontrolę nad gestem)
               
Log.d(TAG, "ACTION_CANCEL: Anulowanie zdarzenia.");
               
break;
        }

       
// Zwracamy wynik metody nadrzędnej dla nieobsłużonych zdarzeń
       
return super.onTouchEvent(event);
    }
}

 

Przykład wyświetlania i skalowania obrazu Bitmap pobranego z pliku zasobów:

 

Pliki graficzne (drawable):

pwsz.jpg

 

Obraz zawierający okno, niebo, architektura, fasada

Zawartość wygenerowana przez AI może być niepoprawna.       Obraz zawierający niebo, okno, architektura, budynek

Zawartość wygenerowana przez AI może być niepoprawna.

 

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.NonNull;

public class Grafika extends View {
    Context
context;
   
int x=-200, y=-200;
   
int s=getWidth();
   
int w=getHeight();
    Bitmap
bitmap;

   
public Grafika(Context context, AttributeSet attrs) {
       
super(context, attrs);
       
this.context = context;
       
bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.pwsz);
    }

   
@Override
   
protected void onDraw(@NonNull Canvas canvas) {
       
super.onDraw(canvas);
       
if(bitmap!=null)
            canvas.drawBitmap(
bitmap, 0, 0, null);
    }

   
@Override
   
public void onSizeChanged (int ww, int hh, int oldw, int oldh) {
       
super.onSizeChanged(ww, hh, oldw, oldh);
       
s = ww;
       
w = hh;
       
bitmap=bitmap.createScaledBitmap(bitmap, s, w, true); // Skalowanie obrazka
   
}
}

 

 

Przykład obrazujący umieszczanie widoku pochodzącego z klasy Java w pliku XML

 

Obraz zawierający budynek, zrzut ekranu, okno, na wolnym powietrzu

Zawartość wygenerowana przez AI może być niepoprawna.

 

Plik widoku xml z zaimplementowanym widokiem pochodzącym z klasy java

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
   
xmlns:app="http://schemas.android.com/apk/res-auto"
   
xmlns:tools="http://schemas.android.com/tools"
   
android:id="@+id/main"
   
android:orientation="vertical"
   
android:layout_width="match_parent"
   
android:layout_height="match_parent"
   
tools:context=".MainActivity">s

    <TextView
       
android:layout_marginTop="20dp"
       
android:layout_gravity="center"
       
android:layout_width="wrap_content"
       
android:layout_height="wrap_content"
       
android:text="Program graficzny"
       
android:textSize="24dp"/>

    <com.example.grafika.Grafika
       
android:layout_weight="1"
       
android:layout_width="match_parent"
       
android:layout_height="match_parent"/>
    <com.example.grafika.Grafika
       
android:layout_weight="1"
       
android:layout_width="match_parent"
       
android:layout_height="match_parent"/>

<Button
   
android:id="@+id/button"
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"
   
android:text="Zamknij"
   
android:layout_marginBottom="20dp"
   
android:layout_gravity="center"/>

</LinearLayout>

 

Część pliku MainActivity:

 

@Override
protected void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
 
//  setContentView(new Grafika(this, null));
}

 

Animacje

System Animation (np. AlphaAnimation, TranslateAnimation) to starszy system, skupiający się na efektach wizualnych na poziomie widoku (ang. tweening).

 

Ograniczenie: Ten system zmienia tylko to, gdzie widok jest rysowany. Prawdziwa pozycja i obszar klikalny (bounding box) pozostają w pierwotnym miejscu.

 

Przykład: Jeśli animujesz przycisk za pomocą TranslateAnimation od lewej do prawej, zobaczysz, jak przycisk przesuwa się w prawo, ale jeśli spróbujesz go kliknąć, system oczekuje kliknięcia w pierwotnym miejscu, z którego widok wystartował.

Wniosek: Zawsze, gdy jest to możliwe, należy używać nowoczesnego systemu Animator (Property Animation).

 

Android udostępnia mechanizm tworzenia animacji opisanych w plikach XML w katalogu:

 

 

Animacje widoku (View Animation) – folder res/anim/

Te animacje działają na widokach (np. ImageView, Button) i obejmują:

Można je łączyć w AnimationSet.

 

Przykład 1 Prosta animacja zanikania (fade_out.xml)

res/anim/fade_out.xml

 

<?xml version="1.0" encoding="utf-8"?>
<alpha
xmlns:android="http://schemas.android.com/apk/res/android"
   
android:fromAlpha="1.0"
   
android:toAlpha="0.0"
   
android:duration="1000" />

 

Wywołanie w Java:

Animation fade = AnimationUtils.loadAnimation(this, R.anim.fade_out);
imageView.startAnimation(fade);

 

Przykład 2 — Animacja przesunięcia

res/anim/slide_in.xml

 

<?xml version="1.0" encoding="utf-8"?>
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
   
android:fromXDelta="-100%"
   
android:toXDelta="0%"
   
android:duration="600" />

 

Wywołanie w Java:

Animation slide = AnimationUtils.loadAnimation(this, R.anim.slide_in);
view.startAnimation(slide);

 

 

Przykład 3 — Zestaw animacji

res/anim/anim_set.xml

 

<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android"
   
android:interpolator="@android:anim/accelerate_decelerate_interpolator">

    <scale
       
android:fromXScale="0.5"
       
android:toXScale="1"
       
android:fromYScale="0.5"
       
android:toYScale="1"
       
android:duration="400"/>

    <alpha
       
android:fromAlpha="0"
       
android:toAlpha="1"
       
android:duration="400"/>
</set>

 

Wywołanie w Java:

 

Animation animSet = AnimationUtils.loadAnimation(this, R.anim.anim_set);
view.startAnimation(animSet);

 

Animacje właściwości (Property Animation) – folder res/animator/

 

System Animator (np. ValueAnimator, ObjectAnimator) jest nowoczesny i potężny. Zmienia on rzeczywistą wartość właściwości obiektu, a nie tylko jego wygląd.

 

Jeśli animujesz przycisk za pomocą ObjectAnimator od lewej do prawej, jego nowa, końcowa pozycja jest rzeczywista – użytkownik może go kliknąć w nowym miejscu.

 

 

Przykład 4 Animacja obrotu

res/animator/rotate.xml

 

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
   
android:propertyName="rotation"
   
android:duration="1000"
   
android:valueFrom="0"
   
android:valueTo="360"
   
android:valueType="floatType" />

 

Wywołanie w Javie:

 

Animator animator = AnimatorInflater.loadAnimator(this, R.animator.rotate);
animator.setTarget(button);
animator.start();

 

interpolator

Interpolator definiuje jak zmienia się postęp animacji w funkcji czasu.
Animacja zawsze ma „ułamek” postępu f od 0.0 do 1.0 (prosty liniowy przebieg: równomierny wzrost). Interpolator przekształca ten ułamek f na wartość g(f) używaną do wyliczenia aktualnej wartości animowanej własności. Innymi słowy: interpolator decyduje o tempo animacji — przyspiesza, zwalnia, odbija, itp.

 

Typy:

 

Interpolator umieszczamy w:

  1. W XML animacji (atrybut):

o   w res/anim/ (View Animation) używasz android:interpolator="@android:anim/..."

o   w res/animator/ (Property Animator) możesz użyć android:interpolator="..." lub ustawić w kodzie

  1. W kodzie Java: wywołanie setInterpolator(...) na Animation lub Animator.

 

Przykłady (XML i Java)

bounce_button.xml (res/animator/bounce_button.xml)

 

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
   
android:propertyName="translationY"
   
android:valueFrom="0"
   
android:valueTo="-120"
   
android:duration="600"
   
android:interpolator="@android:anim/bounce_interpolator"
   
android:valueType="floatType" />

 

Wywołanie w Javie:

Animator anim = AnimatorInflater.loadAnimator(this, R.animator. bounce_button);
anim.setTarget(button);
anim.start();

 

Inne  przykłady:

 

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
   
android:propertyName="rotation"
   
android:duration="2000"
   
android:valueFrom="0"
   
android:valueTo="360"
   
android:interpolator="@android:anim/linear_interpolator"
   
android:valueType="floatType"/>

 

 

4. Zadania

 

Zadanie 1.

Napisz program wykorzystujący tryb graficzny, w którym utwórz dowolny obraz graficzny, początkowo wyświetlany na środku ekranu. Program powinien udostępniać funkcję, które pozwolą na przesuwanie obrazka palem po ekranie.

 

Zadanie 2.

Napisz program wykorzystujący tryb graficzny oraz wątki aby stworzyć  grę pozwalającą odbijać piłeczkę belką oraz zbijać umieszczone powyżej cegły.

Obraz zawierający zrzut ekranu, Prostokąt, kwadrat, Grafika

Zawartość wygenerowana przez AI może być niepoprawna.