Grafika 2D
//Paint - jak rysowac?
//Canvas - co rysowac?
0. Tworzymy aplikację Android:
a. Nazwa: Grafika2D
b. Template: Blank App
c. Minimum Android Version: Android 5.0 (Lollipop)
1. Plik activity_main.xml niewiele nas obchodzi, bo stworzymy własny widok w kodzie,
który nie będzie opierał się na pliku Resource/layout/activity_main.axml. Ten ostatni można nawet usunąć.
2. Jeżeli chcemy wymusić orientację poziomą aplikacji na urządzeniu, musimy zmienić manifest. Ale nie bezpośrednio, a zmieniając atrybut klasy MainActivity:
[Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true, ScreenOrientation = Android.Content.PM.ScreenOrientation.Landscape)]
public class MainActivity : AppCompatActivity
{
Możemy też ustawić domyślne Android.Content.PM.ScreenOrientation.Unspecified i obrócić emulator (ikony na bocznym pasku).
3. Do projektu dodajemy plik klasy Grafika2DView.cs i uzupełniamy kod klasy:
using Android.Content;
using Android.Graphics;
using Android.Util;
using Android.Views;
namespace Grafika2D
{
public class Grafika2DView : View
{
private Paint paint;
int szerokość, wysokość;
public Grafika2DView(Context context, IAttributeSet attributeSet)
:base(context, attributeSet)
{
paint = new Paint(PaintFlags.AntiAlias);
paint.SetStyle(Paint.Style.FillAndStroke);
paint.StrokeWidth = 3;
szerokość = this.Width;
wysokość = this.Height;
Log.Debug("View.ctor", "Szerokość: " + szerokość.ToString());
Log.Debug("View.ctor", "Wysokość: " + wysokość.ToString());
SetBackgroundColor(Color.Cyan);
}
}
}
Uwaga! Rozmiary widoku nie są znane w konstruktorze. Tu odczytana szerokość i wysokość będą równe zeru.
Te parametry należy odczytać w metodzie Grafika2DView.OnSizeChanged.
4. Zróbmy to. Przy okazji uwrażliwimy aplikację na zmianę orientacji ekranu:
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
szerokość = Width;
wysokość = Height;
Log.Debug("View.onSizeChanged", "Szerokość: " + szerokość.ToString());
Log.Debug("View.onSizeChanged", "Wysokość: " + wysokość.ToString());
}
7. Instrukcje rysowania należy umieścić w metodzie Grafika2DView.OnDraw:
Na początek przećwiczymy metody Paint.DrawCircle, DrawLine:
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
paint.SetStyle(Paint.Style.FillAndStroke);
paint.StrokeWidth = 3;
//słońce
paint.Color = Color.Yellow;
{
int x0 = 4 * szerokość / 5;
int y0 = wysokość / 4;
int r1 = wysokość / 10;
int r2 = 2 * r1;
canvas.DrawCircle(x0, y0, r1, paint);
int ilePromieni = 8;
double wsp = 2 * Java.Lang.Math.Pi / ilePromieni;
for (int i = 0; i < ilePromieni; ++i)
{
canvas.DrawLine(x0, y0, x0 + r2 * (float)Java.Lang.Math.Cos(wsp * i), y0 + r2 * (float)Java.Lang.Math.Sin(wsp * i), paint);
}
}
}
8. Aby zmienić widok zmieniamy argument metody SetContentView w metodzie MainActivity.OnCreate:
namespace Grafika2D
{
[Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true, ScreenOrientation = Android.Content.PM.ScreenOrientation.Unspecified)]
public class MainActivity : AppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
//SetContentView(Resource.Layout.activity_main);
SetContentView(new Grafika2DView(ApplicationContext, null));
}
}
}
9. W metodzie Grafika2DView.OnDraw nie powinno być metod inicjujących obiekty.
Ze względu na wydajność te należy utworzyć np. w OnSizeChanged.
a. Deklarujemy prywatne pola klasy Grafika2DView:
private Rect prost1, prost2, prost3;
b. Definiujemy je w metodzie onSizeChanged:
//obiekty wykorzystywane w OnDraw
prost1 = new Rect(0, wysokość / 2, szerokość, wysokość);
prost2 = new Rect(szerokość / 10, wysokość / 3, 5 * szerokość / 10, 2 * wysokość / 3);
prost3 = new Rect(7 * szerokość / 40, 9 * wysokość / 20, 5 * szerokość / 20, 2 * wysokość / 3);
c. Wykorzystujemy je w onDraw dodajac do niej polecenia:
//ziemia
paint.Color = Color.Argb(255, 191, 255, 0);
canvas.DrawRect(prost1, paint);
//ściany
paint.Color = Color.White;
canvas.DrawRect(prost2, paint);
//drzwi
paint.Color = Color.Argb(255, 0, 125, 0);
canvas.DrawRect(prost3, paint);
//okno
paint.Color = Color.Cyan;
for (int ix = 0; ix < 2; ++ix)
{
for (int iy = 0; iy < 2; ++iy)
{
int x0 = 13 * szerokość / 40;
int y0 = 2 * wysokość / 5;
int x1 = x0 + ix * szerokość / 15;
int y1 = y0 + iy * wysokość / 15;
int x2 = x1 + szerokość / 20;
int y2 = y1 + wysokość / 20;
canvas.DrawRect(new Rect(x1, y1, x2, y2), paint);
}
}
10. Usunięcie paska tytułu - zmieniamy temat określony w pliku Resources/values/styles.xml
11. Rysowanie dowolnego kształtu - definiowanie ścieżek (wracamy do edycji klasy Grafika2DView)
a. Pole:
private Path ścieżka;
b. W metodzie OnSizeChanged (polecenia a la Logo):
ścieżka = new Path();
ścieżka.SetFillType(Path.FillType.EvenOdd);
ścieżka.MoveTo(szerokość / 10, wysokość / 3);
ścieżka.LineTo(2 * szerokość / 10, wysokość / 6);
ścieżka.LineTo(4 * szerokość / 10, wysokość / 6);
ścieżka.LineTo(5 * szerokość / 10, wysokość / 3);
ścieżka.LineTo(szerokość / 10, wysokość / 3);
ścieżka.Close();
c. Wreszcie w OnDraw:
//dach
paint.Color = Color.Red;
canvas.DrawPath(ścieżka, paint);
12. Rysowanie bitmapy z zasobow:
a. Pole:
private Bitmap obraz;
b. Metoda OnSizeChanged:
obraz = BitmapFactory.DecodeResource(Resources, Resource.Mipmap.ic_launcher);
if (obraz == null) Log.Error("View.ctor", "Obraz nie został poprawnie wczytany");
Uwaga! Niestety w Xamarin rysunek zielonego robota zastąpiła ikona "X"
c. Metoda OnDraw:
//obraz
{
int x0 = szerokość / 2;
int y0 = 4 * wysokość / 10;
float wsp = (float)obraz.Height / obraz.Width;
int dx = szerokość / 5;
int dy = (int)(y0 * wsp);
canvas.DrawBitmap(obraz, null, new Rect(x0, y0, x0 + dx, y0 + dy), paint);
}
13. Animacja rysunku androida (cykliczne przesuwanie w lewo i w prawo):
a. Definiujemy pola:
private long aktualnyCzas = -1, poprzedniCzas = -1;
b. W metodzie OnDraw:
//czas do animacji
if (aktualnyCzas < 0)
{
aktualnyCzas = Java.Lang.JavaSystem.CurrentTimeMillis();
poprzedniCzas = aktualnyCzas;
}
else
{
poprzedniCzas = aktualnyCzas;
aktualnyCzas = Java.Lang.JavaSystem.CurrentTimeMillis();
}
const double f = 0.001;
//android - animacja
{
int x0 = szerokość / 2;
int y0 = 4 * wysokość / 10;
float wsp = (float)obraz.Height / obraz.Width;
int dx = szerokość / 5;
int dy = (int)(y0 * wsp);
int xs = (int)(szerokość / 4 * Java.Lang.Math.Sin(f * aktualnyCzas)); //dodane!!!
canvas.DrawBitmap(obraz, null, new Rect(x0 + xs, y0, x0 + dx + xs, y0 + dy), paint); //zmienione!!!
}
Invalidate(); //animacja, wymuszamy kolejne wywolanie OnDraw - nie do końca eleganckie, lepiej timer
}
10. Płot na pierwszym planie
Kolory niestandardowe - najlepiej umiescic definicję w zasobach:
[Zadanie dla studentów!!]
a. Do res/values/colors.xml dodajemy:
#2c3e50
#1B3147
#3498db
#FFCD7F32 <--------------------
b. W metodzie Grafika2DView.OnDraw (przed wywolaniem metody Invalidate!!):
//płot
//paint.Color = Color.Argb(255,150,75,0);
//paint.Color = Resources.GetColor(Resource.Color.brazowy); //obsolete
paint.Color = new Color(Android.Support.V4.Content.ContextCompat.GetColor(Context, Resource.Color.brazowy));
{
int x0 = szerokość / 15;
int y0 = 6 * wysokość / 10;
int dx = szerokość / 25;
int y1 = 9 * wysokość / 10;
canvas.DrawRect(new Rect(x0, y0 + dx, szerokość - x0 - dx, y0 + 3 * dx / 2), paint);
canvas.DrawRect(new Rect(x0, y1 - 3 * dx / 2, szerokość - x0 - dx, y1 - dx), paint);
for (int x = x0; x <= szerokość - x0 - dx; x += x0)
{
canvas.DrawRect(new Rect(x, y0, x + dx, y1), paint);
canvas.DrawArc(new RectF(new Rect(x, y0 - dx / 2, x + dx, y0 + dx / 2)), 180, 180, true, paint); //te wszystkie obiekty nie powinny byc tu inicjowane
}
}
Invalidate();
Uwaga! Wszystkie te obiekty, włączając kolor, powinny być inicjowane gdzie indziej.
11. "Rysowanie" napisu z częstością odświeżania. W metodzie onDraw (przed wywolaniem invalidate!!):
//napis
paint.Color = Color.Black;
paint.StrokeWidth = 0.3f;
paint.TextSize = 30f;
//string tekst = "(c) Jacek Matulewski 2018";
double interwał = aktualnyCzas - poprzedniCzas;
interwał /= 1000; //[ms] -> [s]
double fi = Java.Lang.Math.Round(100 / interwał) / 100.0;
string tekst = "FPS: " + string.Format("{0:00.00}", fi); //f w [Hz]=[1/s]=[FPS]
float w = paint.MeasureText(tekst);
float margines = wysokość / 30;
canvas.DrawText(tekst, szerokość - w - margines, wysokość - margines, paint);
Invalidate();