|
|
Kierunek Informatyka
|
|||
|
|
||||
Instrukcja do ćwiczeń laboratoryjnych nr:
|
9
|
Nazwa
przedmiotu:
|
||
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:

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

<?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):

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

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:
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
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.
