Naučte sa vyvíjať Android aplikácie – grafika II

8

Ak chcete kresliť zložitejšiu grafiku, prípadne grafiku, ktorá sa často aktualizuje, využijete s výhodou kreslenie na Canvas. Objekt Canvas znamená v praxi presne to, čo jeho preklad do slovenčiny – maliarske plátno, ktoré slúži ako podklad na vykresľovanie. Canvas umožňuje vykresľovanie objektov do súradnicového systému.

Kreslenie na plátno (canvas) s následným zobrazením grafiky na obrazovke zariadenia je principiálne možné realizovať dvoma spôsobmi:

využitím existujúceho objektu View – v prípade ak chcete doplniť statické grafické prvky k už existujúcim prvkom prostredníctvom existujúceho plátna

prostredníctvom novo vytvoreného plátna – v prípade dynamického obsahu, ktorý má byť pravidelne menený a prekresľovaný pričom v tom istom vlákne voláme metódu invalidate() a v samostatnom vlákne používame manažment cestou triedy SurfaceView, ktorá sprístupňuje aktuálnu plochu cestou triedy SurfaceHolder (getHolder(), SurfaceHolder.Callback), sprístupnenie plátna v rámci samostatného vlákna je v tomto prípade riešené metódou lockCanvas() a jeho odblokovanie po vykreslení všetkých požadovaných prvkov metódou unlockCanvasAndPost()

Najčastejšie budete v súvislosti s kreslením na Canvas využívať metódy  drawText(), drawPoints(), drawColor(), drawOval() a drawBitmap().

Canvas umožňuje pokročilejšie vykresľovanie, môžete nastaviť rôzne farby a štýly, hrúbku čiar, veľkosť textu. Môžete nastaviť rôzne metódy grafickej optimalizácie, napríklad antialiasing na vyhladenie obrazu, aby sa odstránili zubaté šikmé čiary a podobne. Pre naznačené účely budete používať metódy setStrokeWidth(), setTextSize(), setColor(), či setAntiAlias().

Vykresľovanie na Canvas budeme demonštrovať na triviálnom príklade v ktorom vykreslíme obdĺžnik, čiaru a text.

Najskôr vytvoríme triedu Kreslenie

package com.example.grafika5;
 
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
 
public class Kreslenie extends View {
    public Kreslenie(Context context){super(context);}
    @SuppressLint("DrawAllocation") @Override
    protected void onDraw(Canvas canvas)  {
        Paint paint = new Paint();
        paint.setColor(Color.GREEN);
        canvas.drawRect(10, 10, 500, 250, paint);
        paint.setColor(Color.BLUE);
        paint.setTextSize((float) 60.0);
        canvas.drawText("Graficky výpis textu", 10, 300, paint);
        canvas.drawLine(10, 430, 500, 430, paint);
    }
}

V kóde hlavnej aktivity nastavíme túto triedu ako kontextové View pre vykresľovanie v metóde SetContentView(). Z uvedeného vyplýva, že XML návrh hlavnej aktivity sa vôbec nepoužije

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new Kreslenie(this));
    }
}

Kreslenie dotykom na Canvas

U prístrojov s dotykovým displejom sa námet na ďalšiu grafickú aplikáciu, plnú dynamiky ponúka sám –kresliť prstom po obrazovke. Pre tento účel trochu prerobíme triedu Kreslenie z predchádzajúceho príkladu. 

Princíp je jednoduchý. V obslužnej procedúre onTouchEvent budeme snímať ako udalosti dotyky (MotionEvent.ACTION_DOWN) a pohyby prsta (MotionEvent.ACTION_MOVE) a ukladať súradnice kde ste sa displeja dotkli do poľa. Pre jednoduchosť budeme serializovať, to znamená ukladať súradnice x a y sériovo do jednorozmerného poľa. Následne vykreslíme čiaru ako spojnicu medzi dvomi poslednými bodmi. Všimnite si nastavenie hrúbky čiary. 

Po identifikácii udalosti dotyku, alebo posunu prsta sa explicitne vyvolá udalosť invalidate(). V obsluhe tejto udalosti sa zavolá metóda onDraw pre prekreslenie obrazovky

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.View;
 
import java.util.ArrayList;
 
public class Kreslenie extends View {
    public Kreslenie(Context context)  {
        super(context);
    }
 
    ArrayList pt = new ArrayList();
 
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int e = event.getAction();
        if (e == MotionEvent.ACTION_DOWN || e == MotionEvent.ACTION_MOVE)  {
            pt.add(event.getX());  pt.add(event.getY());
            invalidate();
        }
        return true;
    }
 
 
    @Override
    protected void onDraw(Canvas canvas)  {
        Paint paint = new Paint();
        paint.setColor(Color.BLACK);
 
        paint.setStrokeWidth(10);
        for(int i =2; i < pt.size(); i+=2)  {
            canvas.drawLine(pt.get(i-2), pt.get(i-1),
                    pt.get(i), pt.get(i+1) , paint);
        }
    }
}

 

V kóde hlavnej aktivity nastavíme túto triedu ako kontextové View pre vykresľovanie v metóde SetContentView(). XML návrh hlavnej aktivity sa vôbec nepoužije

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new Kreslenie(this));
    }
}

Samozrejme aj tu platí, že za málo námahy, málo efektu, aplikácia má „drobné“ vady, napríklad ak kreslíte viac útvarov, spojí body kde ste pero zdvihli a znovu položili.

Dymamické vykresľovanie na Canvas s využitím View

Vykresľovanie na Canvas môžete realizovať cez View, alebo triedu odvodenú od View,  ako v predchádzajúcich príkladoch. To v prípade ak aktualizácie grafiky budú menej časté. Pri často aktualizovanej grafike  je lepšie použiť špecializovanú triedu SurfaceView.

Plátno je rozhraním medzi aktivitou a obrazovkou, resp. plochou (surface) zariadenia, na ktorú budú vykreslené grafické prvky. Prostredníctvom plátna sú vykonávané konkrétne príkazy určené na kreslenie – tzv. rendering pixelov. V konečnom dôsledku ide o zmenu farieb pixelov obrazovky čím vytvárame požadované grafické tvary. Informácie o farbe pixelov konkrétnych grafických prvkov sú umiestňované do pamäťového bloku objektu Bitmap, ktorý musíme vytvoriť spolu s objektom Canvas.

Námetom príkladu bude pohyb figúrky po ploche. Vzhľadom k tomu, že vykresľovanie cez View sa hodí pre menej často aktualizovanú grafiku, mohli by sme takto realizovať napríklad jednoduchú hru typu šachy. V reálnej aplikácii by sa figúrka pohybovala po  ploche znázorňujúcej hernú scénu, napríklad po šachovnici. 

Návrhový kód hlavnej aktivity bude využívať kontejner RelativeLayout

Skôr než pristúpite k tvorbe kódu na vykresľovanie, je potrebné realizovať dva prípravné kroky: 

  • Do adresára drawable vložte obrázok nejakej figúrky. Obrázok ľahko získate cez Google v sekcii obrázky.
  • V súbore dimens.xml v zložke res\values definujte výšku a šírku obrázku.
    50dp
    50dp

Vykresľovanie sa realizuje v Java kóde hlavnej aktivity. Figúrka mení svoju polohu v sekundových intervaloch.

V metóde onCreate() vytvoríme inštanciu objektu Bitmap na zobrazenie obrázka z resourceov.

Vytvoríme triedu HraView odvodenú od View a pridáme ju do layoutu hlavnej aktivity

rl.addView(hraView);

Všimnite si v konštruktore triedy HraView spôsob získania informácie o rozlíšení obrazovky v horizontálnom aj vertikálnom smere. Pohyb figurky bude realizovaný ako postupné vykresľovanie, pričom jednotlivé iterácie sa realizujú v threade. V každej fáze pohybu je predtým vykreslený obrázok vymazaný a nakreslený na novej pozícii. Pre jednoduchosť meníme súradnice vykresľovania pripočítaním konštanty. Figúrka sa samozrejme vykreslí iba v prípade ak jej súradnice nie sú mimo zobrazovacej plochy. Taktovanie po sekundách  sa vykonáva pomocou metódy 

Thread.sleep(1000); 

Na vykresľovanie sa využíva prekrytá metóda onDraw() a v nej canvas.drawBitmap()

Kompletný kód hlavnej aktivity

package com.example.grafika6;
 
import androidx.appcompat.app.AppCompatActivity;
 
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.widget.RelativeLayout;
 
public class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(Bundle savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        final RelativeLayout rl = (RelativeLayout) findViewById(R.id.activity_main);
        final Bitmap bmp = BitmapFactory.decodeResource(getResources(),
                R.drawable.droid);
        final HraView hraView = new HraView(getApplicationContext(),bmp);
        rl.addView(hraView);
 
        new Thread(new Runnable()  {
            @Override
            public void run()  {
                while (hraView.posun())   {
                    hraView.postInvalidate();
                    try {Thread.sleep(1000);}
                    catch (InterruptedException e)  {
                        Log.i("KreslenieCanvas", "CHYBA Sleep");}
                }
            }
        }).start();
    }
 
    private class Poloha  {
        float sX, sY;
 
        public Poloha(float x, float y)   {
            sX = x;       sY = y;
        }
        synchronized Poloha posun(Poloha dxdy) {
            return new Poloha(sX + dxdy.sX , sY + dxdy.sY);
        }
        synchronized Poloha dejSouradnice()  {
            return new Poloha(sX, sY);
        }
    }
 
 
    private class HraView extends View {
        final private Bitmap mBmp;
        private Poloha mAktualni;
        final private Poloha mDelta;
 
        final private DisplayMetrics mMetrika;
        final private int mWidth;
        final private int mHeight;
        final private int mOld, mNew;
        final private Paint mPainter = new Paint();
 
        public HraView(Context context, Bitmap bitmap)  {
            super(context);
 
            mOld = (int) getResources().getDimension(R.dimen.image_height);
            this.mBmp = Bitmap.createScaledBitmap(bitmap, mOld, mOld, false);
            mNew = mOld + 10;
 
            mMetrika = new DisplayMetrics();
            MainActivity.this.getWindowManager().getDefaultDisplay()
                    .getMetrics(mMetrika);
            mWidth = mMetrika.widthPixels;
            mHeight = mMetrika.heightPixels;
 
            float x = 10; float y = 10;
            mAktualni = new Poloha(x, y);
 
            float dy = y+1;
            float dx = x+1;
            mDelta = new Poloha(dx, dy);
 
            mPainter.setAntiAlias(true);
 
        }
 
        @Override
        protected void onDraw(Canvas canvas)  {
            Poloha pp = mAktualni.dejSouradnice();
            canvas.drawBitmap(mBmp, pp.sX, pp.sY, mPainter);
        }
 
        protected boolean posun()  {
            mAktualni = mAktualni.posun(mDelta);
            if (mAktualni.sY < 0 - mNew
                    || mAktualni.sY > mHeight + mNew
                    || mAktualni.sX < 0 - mNew
                    || mAktualni.sX > mWidth + mNew)
                return false;
            else return true;
        }
    }
 
 
}

 

Rekapitulácia seriálu

1  – Prvá aplikácia 

2 – Možnosti emulátorov 

3 - Zorientujte sa v projekte aplikácie

4 – Princípy dizajnu a škálovania

5 – Uporiadanie prvkov používateľského rozhrania

6 – Obsluha udalostí

7 – Aplikácia s dvomi aktivitami  

8 – Spustenie na reálnom zariadení

9 – Intenty, alebo kto to urobí

10 – Dotyky a gestá

11 - Dotyky a gestá II  

12  - Zmena orientácie displeja

13 – Grafika 1

Zobrazit Galériu

Luboslav Lacko

Všetky autorove články
Android vývoj Android Studio

8 komentárov

Serial v PDF reakcia na: Naučte sa vyvíjať Android aplikácie – grafika II

5.4.2020 21:04
Je veľmi pravdepodobné, že po ukončení seriálu ho vydáme ako PDF. Urobili sme to aj so všetkými článkami o programovaní mikropočítačovej dosky BBC micro:bit
Reagovať

seriál v pdf reakcia na: Naučte sa vyvíjať Android aplikácie – grafika II

5.4.2020 11:04
Zdravím,
bude sa dať tento seriál stiahnuť aj ako tlačiteľné pdf?
Reagovať

subor dimens.xml reakcia na: Naučte sa vyvíjať Android aplikácie – grafika II

4.4.2020 13:04
Je to vysvetlené v predchádzajúcom dieli
do súboru dimens.xml v zložke res\values tribe doplniť tie rozmery. Ak tam tak súbor nie je treba ho vytvoriť
400dp4000dp
Reagovať

Dymamické vykresľovanie na Canvas s využitím View reakcia na: Naučte sa vyvíjať Android aplikácie – grafika II

4.4.2020 11:04
Možno sa predpokladajú, nejaké znalosti z predchádzajúcich lekcii.
Toto neviem vytvoriť:
V súbore dimens.xml v zložke res\values definujte výšku a šírku obrázku.
50dp
50dp

Súbor som asi zvládol, ale ako do neho zapísať výšku a širku aby to apikácia spapala, tak to neviem.
Toto nefunguje:
50dp50dp

Som nesmierne smutný....
Reagovať

Problém s float reakcia na: Naučte sa vyvíjať Android aplikácie – grafika II

4.4.2020 10:04
Problém s float sa podarilo vyriešiť oprava, ktorá funguje pri kreslení dotyom na Canvas. Treba zmeniť v onDraw: canvas.drawLine((float)pt.get(i-2),(float) pt.get(i-1),(float)pt.get(i),(float) pt.get(i+1) , paint);
Reagovať

Kreslenie dotykom reakcia na: Naučte sa vyvíjať Android aplikácie – grafika II

3.4.2020 18:04
Nezabudnite zmeniť v xml súbore hlavnej aktivity ConstraintLayout na Relative Layout
Reagovať

Kreslenie dotykom reakcia na: Naučte sa vyvíjať Android aplikácie – grafika II

3.4.2020 17:04
Kreslenie dotykom na Canvas sa nepodarilo. Hlási to pri tomto zápise canvas.drawLine(pt.get(i-2).... Cannot be aplied to. Pochopil som, že to nevie konvertovať hodnotu poľa na float. Asi teda...
Reagovať

Čierne pozadie reakcia na: Naučte sa vyvíjať Android aplikácie – grafika II

3.4.2020 08:04
Viem, že je to teraz trend. Ale to čierne pozadie na web stránke je veľmi nepríjemné. Hlavne keď to mám čítať na počítači a cez deň, keď mám všade okolo svetlo. Tá čierna je veľmi nepríjemná. K tomu tie biele obrázky na obrazovke sú tak akurát na vypálenie očí. Toto sa nepodarilo!!
Reagovať

Pridať komentár

Mohlo by vás zaujímať

Mohlo by vás zaujímať