Naučte sa vyvíjať aplikácie pre Android za 14 dní – dotyky a gestá

1

Implicitné ovládanie platformy Android je pomocou dotykov a giest na dotykovej obrazovke. Moderné zariadenia umožňujú snímať dotyky viacerých prstov, vrátane dynamiky a tlaku.

Multi dotykový displej dokáže zaregistrovať a sledovať viac dotykov v rovnakom čase. Preto každý z týchto dotykových zdrojov je reprezentovaný ako ukazovateľ. Ak systém identifikuje nový zdroj dotyku, vytvorí preň jedinečný identifikátor, ktorý je k dispozícii po doby aktivity zdroja dotyku. V niektorých prípadoch, Android vytvorí viac ukazovateľov v rámci jednej udalosti pohybu. Ku každému ukazovateľu v rámci udalosti pohybu je možné pristupovať prostredníctvom jeho indexu. 

Na spracovanie pohybových giest z rôznych vstupných zariadení sa využíva trieda MotionEvents. Obsahuje kód akcie, teda gesta a jeho parametre, teda súradnice pohybu, jeho dynamiku tlak a podobne.

Kódy akcií sú:

  • ACTION_DOWN - prvý prst sa dotkol obrazovky 
  • ACTION_POINTER_DOWN - predtým už nastala udalosť  ACTION_DOWN, a teraz sa ďalší prst dotkol obrazovky
  • ACTION_POINTER_UP - predtým už nastali udalosti ACTION_POINTER a ACTION_POINTER_DOWN, a teraz sa jeden z prstov prestal dotýkať obrazovky
  • ACTION_MOVE - niektoré z prstov, ktoré sa dotýkajú obrazovky zmenili svoju polohu
  • ACTION_UP - posledný prstov, ktoré sa dotýkali obrazovky sa jej prestal dotýkať 
  • ACTION_CANCEL – niečo spôsobilo zrušenie aktuálneho gesta. 

Android sa pri interpretácií dotykov snaží o zachovanie konzistencie, to znamená, že dotyky nasledujú jeden po druhom a budú sa pohybovať v skupine. 

Pre spracovanie pohybu prstov po dotykovej obrazovke môžete použiť niektorú z nasledujúcich metód: 

  • getActionMasked()  - metóda vracia kód akcie spojenej s pohybom 
  • getActionIndex() – metóda vráti index ukazovateľa spojeného kódom akcie. Takto môžete zistiť index ukazovateľa, ktorý sa práve dotkol obrazovky.
  • getPointerId(int pointerIndex) – vráti identifikátor  ukazovateľa so zadaným indexom. 
  • GetPointerCount() - metóda vráti počet ukazovateľov zainteresovaných na aktuálnom pohybe.
  • getX(int pointerIndex) – vráti X- súradnicu ukazovateľa so zadaným indexom
  • getY(int pointerIndex) – vráti Y súradnicu ukazovateľa so zadaným indexom
  • findPointerIndex (int pointerId) – metóda vracia index spojený s identifikátorom ukazovateľa.

Ak sa prvok typu View, respektíve jeho vizuálna reprezentácia stane objektom dotyku, alebo pohybu je volaná metóda View.onTouchEvent(MotionEvent event). Metóda vráti hodnotu true  ak MotionEvent bol spracovaný. V opačnom prípade vráti hodnotu false.

Udalosti spojené s dotykmi sú spracované cez listener. View.OnTouchListener definuje callback metódy, ktoré budú volané pri udalostiach, ako je zatlačenie, uvoľnenie, alebo potiahnutie prstom po obrazovke. Listener sa registruje pomocou metódy  View.setOnTouchListener(). Znie to zložito, ale v príklade uvidíte, že je to jednoduché 

V jednoduchých aplikáciách môžete spracovávať jednotlivé dotykové udalosti nezávisle od seba, no moderné aplikácie často potrebujú spracovávať komplexne viac dotykov, ktoré sú súčasťou zložitejšieho gesta.

Jednoduché ťuknutia dokážete identifikovať ako kombináciu akcií ACTION_DOWN a ACTION_UP. V prípade dvojitého ťuknutia sa táto sekvencia zopakuje dvakrát, teda ako postupnosť akcií ACTION_DOWN, ACTION_UP, ACTION_DOWN a ACTION_UP v rýchlom slede. 

Príklad – zobrazenie udalostí dotyku

V prvom príklade budeme indikovať udalosti súvisiace s dotykmi. Používateľke rozhranie bude obsahovať dva prvky TextView  v hornej časti obrazovky a celá plocha obrazovky bude k dispozícii na dotyky a gestá. Prvky sú vertikálne zapuzdrené v kontejneri typu LinearLayouyt. Všimnite si že Layoutu je priradený ID idemtifikátor  

 <?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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/activity_main"
    tools:context=".MainActivity">
 
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="stav1"
        android:id="@+id/tw1"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="stav2"
        android:id="@+id/tw2"
        />
</LinearLayout>

Úmyselne vkladáme do návrhového XML kódu textové reťazce priamo, aby bol príklad čo najjednoduchší. Určite ho nebudeme prekladať do iných jazykov. 

Kľúčové dejstvo aplikačnej logiky sa odohráva v metóde na obsluhu udalostí od dotykov a pohybov po dotykovej obrazovke ObsluhaDotyku(). Všimnite si, ako sa zisťuje poloha dotykov, ich identifikátory a kódy aktuálnych akcií.

Kompletný kód hlavnej aktivity

public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        LinearLayout lLayout =
                (LinearLayout)findViewById(R.id.activity_main);
 
        lLayout.setOnTouchListener(
                new LinearLayout.OnTouchListener() {
                    public boolean onTouch(View v,
                                           MotionEvent me) {
                        ObsluhaDotyku(me);
                        return true;
                    }
                }
        );
 
    }
 
    void ObsluhaDotyku (MotionEvent me)
    {
        TextView tw1 = (TextView)findViewById(R.id.tw1);
        TextView tw2 = (TextView)findViewById(R.id.tw2);
 
        int pointerCount = me.getPointerCount();
 
        for (int i = 0; i < pointerCount; i++)
        {
            int x = (int) me.getX(i);
            int y = (int) me.getY(i);
            int id = me.getPointerId(i);
            int akcia = me.getActionMasked();
            int nAI = me.getActionIndex();
            String sAkcia;
 
 
            switch (akcia)
            {
                case MotionEvent.ACTION_DOWN:
                    sAkcia = "DOWN";
                    break;
                case MotionEvent.ACTION_UP:
                    sAkcia = "UP";
                    break;
                case MotionEvent.ACTION_POINTER_DOWN:
                    sAkcia = "PNTR DOWN";
                    break;
                case MotionEvent.ACTION_POINTER_UP:
                    sAkcia = "PNTR UP";
                    break;
                case MotionEvent.ACTION_MOVE:
                    sAkcia = "MOVE";
                    break;
                default:
                    sAkcia = "";
            }
 
            String sStav = "Akcia: " + sAkcia + " Index: " + nAI + " ID: " + id + " X: " + x + " Y: " + y;
 
            if (id == 0) tw1.setText(sStav);
            else         tw2.setText(sStav);
        }
    }
}

Pri spustení na emulátore odporúčame po prvom dotyku stlačiť kláves CTRL, tak budete môcť emulovať myšou dotyk dvoma prstami, Keď sa s aplikáciou pohráte, odporúčame znovu si prečítať stať o význame kódov jednotlivých akcií súvisiacich s dotykovým ovládaním.

 

Príklad – zobrazenie viacnásobných dotykov

V ďalšom príklade budeme indikovať viacnásobné dotyky na obrazovke. Podobne ako sa zaužívala aplikácia s námetom „hello world“ prvá aplikáciu začiatočníka pre vývoj na danej platforme aj pre prvé kroky sa zaužívala aplikácia indikujúca dotyky pomocou farebných kruhov. Aplikácia nakreslí väčší kruh v mieste kde používateľ prstom dotkne obrazovky. Farba kruhu je náhodne vybraná. Ak používateľ  pohybuje prstom po obrazovke, kruh sa premiestňuje. Ak sa požívateľ dotýka obrazovky na viacerých miestach, zobrazí sa viac menších kruhov. Čím viac dotykov súčasne, tým budú kruhy menšie.

V layoute hlavnej aktivity využijeme pre vykresľovanie FrameLayout

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/plocha"
    tools:context="sk.pcrevue.llacko.multidotyk2.MainActivity">
 
</FrameLayout>
V zložke drawable definujeme v súbore znacka.xml grafický prvok kruh, ktorý sa bude vykresľovať
 
<shape
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="oval">
  <size android:width="10dip" android:height="10dip"/>
  <stroke android:color="#FFFF" android:width="2dip" 
          android:dashWidth="1dip" android:dashGap="4dip"/>
</shape>

Vytvorte triedu DotykView, ktorá bude mať na starosti vizuálnu reprezentáciu dotyku pomocou kruhu s náhodne vygenerovanou farbou. Trieda eviduje aj počet súčasných dotykov a podľa tohto počtu nepriamo úmerne určuje priemer kruhu. Všimnite si, že počet dotykov nemôže byť nula, pretože sa týmto číslom delí. Kód triedy môžete pridať do súboru s kódom hlavnej aktivity MainActivity.java

class DotykView extends View
{
    private float xx, yy;
    final static private int MAX_SIZE = 400;
    private int nDotykov = 0;
    final private Paint mPaint = new Paint();
 
    public DotykView(Context context, float x, float y)
    {
        super(context);
        xx = x;
        yy = y;
        mPaint.setStyle(Paint.Style.FILL);
        Random rnd = new Random();
        mPaint.setARGB(255, rnd.nextInt(256), rnd.nextInt(256),
                rnd.nextInt(256));
    }
 
    float getXLoc() {return xx;}
    void setXLoc(float x) {xx = x;}
    float getYLoc() {return yy;}
    void setYLoc(float y) {yy = y;}
 
    void pocetDotykov (int nDotykov) {nDotykov = nDotykov;}
 
    @Override
    protected void onDraw(Canvas canvas)
    {
        canvas.drawCircle(xx, yy, MAX_SIZE / (nDotykov+1), mPaint);
    }
}

 

V kóde hlavnej aktivity je potrebné v statických poliach udržiavať údaje o aktuálnych dotykoch. Polia sa dynamicky plnia v metóde onTouch().

  • Ak je kód akcie je ACTION_DOWN alebo  ACTION_POINTER_DOWN, potom pribudol prvý, alebo ďalší dotyk. Vtedy sa vytvorí nový kruh na označenie aktuálneho miesta dotyku. 
  • Ak je kód akcie ACTION_UP, alebo ACTION_POINTER_UP, znamená to, že používateľ zdvihol niektorý prst z obrazovky. Vtedy odstránime zodpovedajúci kruh. Zistíme jeho index a následne ID.
  • Ak je kód akcie ACTION_MOVE potom sa prekreslia kruhy zodpovedajúce dotykom prstov, ktoré sa pohli. V aplikácii inicializujeme prekreslenie len ak je pohyb väčší než 3 dip. 
public class MainActivity extends AppCompatActivity {
 
    private static final int MIN_DELTA = 2;
 
    final private static LinkedList<DotykView> neaktivneDotyky =
        new LinkedList<DotykView>();
    @SuppressLint("UseSparseArrays")
    final private static Map<Integer, DotykView> aktivneDotyky =
                        new HashMap<Integer, DotykView>();
 
     private FrameLayout frame;
 
       @Override
       public void onCreate(Bundle savedInstanceState)
       {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_main);
           frame = (FrameLayout) findViewById(R.id.plocha);
           initViews();
 
           frame.setOnTouchListener(new View.OnTouchListener()
           {
               @Override
               public boolean onTouch(View v, MotionEvent me)
               {
                   switch (me.getActionMasked())
                   {
                       case MotionEvent.ACTION_DOWN:
                       case MotionEvent.ACTION_POINTER_DOWN:
                       {
                           int index = me.getActionIndex();
                           int id = me.getPointerId(index);
 
                           DotykView dw = neaktivneDotyky.remove();
                           if (dw != null)
                           {
                               aktivneDotyky.put(id, dw);
                               dw.setXLoc(me.getX(index));
                               dw.setYLoc(me.getY(index));
                               aktualizujDotyky(aktivneDotyky.size());
                               frame.addView(dw);
                           }
                           break;
                       }
 
                       case MotionEvent.ACTION_UP:
                       case MotionEvent.ACTION_POINTER_UP:
                       {
                           int index = me.getActionIndex();
                           int id = me.getPointerId(index);
 
                           DotykView dw = aktivneDotyky.remove(id);
                           if (dw != null)
                           {
                               neaktivneDotyky.add(dw);
                               aktualizujDotyky(aktivneDotyky.size());
                               frame.removeView(dw);
                           }
                           break;
                       }
 
                       case MotionEvent.ACTION_MOVE:
                       {
                           for (int idx = 0; idx < me.getPointerCount();
                                idx++)
                           {
                               int ID = me.getPointerId(idx);
 
                               DotykView dw = aktivneDotyky.get(ID);
                               if (null != dw)
                               {
 
                                   // pouze pri pohybu nad minimalni vzdalenost
                                   if (Math.abs(dw.getXLoc() -me.getX(idx)) > MIN_DELTA
                                           || Math.abs(dw.getYLoc()- me.getY(idx)) > MIN_DELTA)
                                   {
                                       dw.setXLoc(me.getX(idx));
                                       dw.setYLoc(me.getY(idx));
                                       dw.invalidate();
                                   }
                               }
                           }
                           break;
                       }
                       default:
                   }
                   return true;
               }
 
               private void aktualizujDotyky(int numActive)
               {
                   for (DotykView dw : aktivneDotyky.values())
                       dw.pocetDotykov(numActive);
            }
        });
    }
 
    private void initViews()
    {
        for (int idx = 0; idx < 10; idx++)
        {
            neaktivneDotyky.add(new DotykView(this, -1, -1));
        }
    }
}

 

Aplikáciu otestujte tak, že najskôr priložte jeden prst na obrazovku a pohybujte ním. Sledujte prekresľovanie kruhu. Potom priložte ďalšie prsty a pohybujte nimi po obrazovke. 

Gestám sa budeme venovať aj v ďalšom pokračovaní

 

Rekapitulácia seriálu

1 deň – Prvá aplikácia 

2 deň – Možnosti emulátorov 

3 deň - Zorientujte sa v projekte aplikácie

4 deň – Princípy dizajnu a škálovania

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

6 deň – Obsluha udalostí

7 deň – Aplikácia s dvomi aktivitami  

8 deň – Spustenie na reálnom zariadení

9 deň – Intenty, alebo kto to urobí

Luboslav Lacko

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

1 komentár

Super funguje reakcia na: Naučte sa vyvíjať aplikácie pre Android za 14 dní – dotyky a gestá

29.3.2020 14:03
Tento článok je veľmi zaujímavý a hlavne všetko funguje. Výborne...

Pridať komentár

Mohlo by vás zaujímať

Mohlo by vás zaujímať