Tvorba aplikací pro Android

06

Uspořádání prvků v okně
(Layouts)

Grafické prvky v okně (tlačítka, tex­tová pole atd.) v An­droidu vkládáme do kom­ponent, kterým se říká layout. Layout grafické prvky shromaž­duje (fun­guje jako kon­tejn­er grafic­kých prvků) a především určuje uspořádání prvků v okně a je­jich vzájem­nou polohu (fun­guje jako správce rozložení). V českém textu budeme používat buď slovo kon­tejn­er, nebo an­glické slovo layout tam, kde by překlad byl zavádějící. Termín layout se běžně používá i v českých tex­tech o pro­gramování a tvorbě grafic­kého rozhraní.


Běžně používané layouty

Zatím jsme prvky v okně skládali pod sebe nebo vedle sebe a používali k je­jich uspořádání kon­tejn­er LinearLayout. Tento kon­tejn­er je pro naše účely nej­jednodušší za před­pokladu, že nám stačí skládat prvky pod sebe nebo vedle sebe. Pro složitější rozložení se sice kon­tejne­ry dají vnořovat, ale to vede k výpočetně náročnějšímu a méně spoleh­livému kódu.


Výchozí vol­bou v An­droid Studiu je RelativeLayout. Ten určuje pozici grafic­kých prvků ve vztahu k os­tatním grafic­kým prvkům. Pro komplex­nější rozložení je tak méně výpočetně náročný než vnořované kon­tejne­ry LinearLayout, ale zase je obtížnější si rozložení prvků představit a up­ravovat.


Dalším základním kon­tejnerem je WebView, který slouží k jedno­duc­hému zob­raz­ení dokumen­tu zap­saného v jazyce HTML.


Ex­is­tují ale i další kon­tejne­ry (layouts). Zmiňme GridView, který uspořádává prvky do pra­videlné mřížky a hodí se tedy typic­ky pro náhledy obrázků a další podobné situace, a ListView, který umožňuje jedno­duše řešit dynamické přidávání tex­tových položek. Tyto kon­tejne­ry používají třídu Adapter k získání položek, které mají zob­razovat. Kon­tejnerem ListView se budeme pod­robněji zabývat v této kapitole.


Layout ListView

ListView slouží k zob­raz­ení několika tex­tových položek, mezi kterými můžeme skrolovat. Je tak vhodný zejména tehdy, kdy počet položek předem neznáme a přidáváme položky teprve za běhu ap­likace.


Umístění ListView do okna

Kon­tejn­er vkládáme stejně jako os­tatní kon­tejne­ry v XML popisu vzhledu okna. Používáme ele­ment <ListView>.


Vkládání dat do kon­tejneru ListView

Ke vkládání položek používá layout ListView in­stan­ce tříd, im­plemen­tujících rozhraní (in­ter­face) Adapter. Nejběžnější je použití třídy ArrayAdapter, která umožňuje vkládat položky z pole či Listu (například z kon­tejneru ArrayList), nebo použití třídy CursorSimpleCursorAdapter. My se omezíme na použití ArrayAdapteru.


ListView lvSeznam = (ListView) this.findViewById(R.id.prehled_zaznamu);
lvSeznam.setAdapter(new ArrayAdapter<Zaznam>(this, android.R.layout.simple_list_item_1, zaznamy));

Adaptér nas­tavíme metodou setAdapter(). Při vytváření ArrayAdapteru zadáváme para­met­ry:

  • Context, tedy ak­tivitu, ve které bude Adapter použit

  • způsob formátování položky – můžeme použít
    android.R.layout.simple_list_item_1

  • a konečně List či pole s daty.

Data navíc musí mít vhodně přek­rytu metodu toString(), která bude použita pro nas­tav­ení textů jedno­tlivých položek ListView.


Ak­tualizace dat

Pokud dojde ke změně dat v ArrayListu, musíme o tom in­for­movat ArrayAdapter tak, že zavoláme jeho metodu notifyDataSetChange(). ArrayAdapter zařídí ak­tualizaci zob­razených dat.


((ArrayAdapter) seznam.getAdapter()).notifyDataSetChanged();

Rea­kce na volbu položky

Rea­kci na stisk některé z položek ListView nas­tavujeme pomocí mech­anis­mu pos­luchačů (Listener). V našem případě použijeme metodu setOnClickListener() třídy ListView a předáme jí jako para­met­ry in­stan­ci potom­ka třídy OnItemClickListener.


Při použití an­onym­ního pos­luchače (Listener) může kód vypadat například takto:


lvSeznam.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> adapterView, View itemView, int poradiPolozkyVAdapteru, long cisloRadkuVListView) {
        uprav(adapterView, itemView, poradiPolozkyVAdapteru,
                cisloRadkuVListView);
    }
});

Metoda uprav(…) je v tomto příkladě metoda třídy, která re­prezen­tuje okno, ve kterém je ListView vnořen. V této metodě reali­zujeme samot­nou rea­kci na stisk položky.


Jaké para­met­ry dos­taneme k dis­pozici:

  • adapterView je v našem případě in­stan­ce ListView, ve které byla položka zvolena. Je tady proto, ab­yc­hom mohli mít li­sten­er společný pro více ListView.

  • itemView je položka v ListView, která byla zvolena.

  • Číslo poradiPolozkyVAdapteru v para­met­ru metody onItemClick(…) udává číslo položky, na kterou uživatel klikl. Jedná se o pořadí položky v adaptéru, tedy v našem případě v ArrayListu. Položky jsou číslovány od 0. Pod tímto číslem lze položku získat z ArrayListu metodou get(…).

  • Číslo cisloRadkuVListView udává číslo řádku v kom­ponentě ListView, který byl zvol­en. Řádky jsou také číslovány od 0 a v našem případě to tedy bude stejná hod­nota jako poradiPolozkyVAdapteru.

Pokud tedy chceme získat záznam, který odpovídá zvol­ené položce, použijeme metody getItemAt­Posi­tion() třídy Li­stView (zděděnou ze třídy Adap­terView, která je nepřímým předkem Li­stView):


Zaznam zaznam = (Zaznam) zvolenyView.getItemAtPosition(poziceVAdapteru);

Příklad: Můj pestrý život

Vytvoříme jedno­duc­hou ap­likaci, která bude umožňovat zaz­namenat, co nového jsme zažili/naučili se/­vyzkoušeli si. Budeme zaz­namenávat jedno­tlivé poznámky. U každé poznámky uvede­me datum, co zajímavého se stalo a případně další údaje, komen­táře atd. Při klik­nutí na poznámku se poznámka zvýrazní jako důležitá, druhé klik­nutí příznak důležitos­ti zruší.



Ap­likaci si můžete snad­no up­ravit na jedno­duchý trénin­kový deník, ...



Jedno­tlivé položky/záznamy budou v ap­likaci načteny do kom­ponen­ty ListView. V dalších kapitolách doplníme možnost přidávat záznamy, up­ravovat je a mazat.


Celá ap­likace bude ve výs­ledku vypadat takto:


Pos­tup – im­plemen­tace Li­stView:

  1. Vytvoříme nový pro­jekt již známým způsobem. V XML návrhu vytvoříme kom­ponen­tu ListView, která umožňuje zob­razit více položek najed­nou:

  2. <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        ...
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Uložené záznamy:" />
        <ListView
            android:id="@+id/lv_seznam"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </ListView>
    </LinearLayout>
    

  3. Přic­hystáme si třídu Zaznam s at­ributy nazevpopis (oba budou tex­tové). Přidáme at­ribut dulezite (logická hod­nota) a případně další at­ributy dle potřeby. Přek­ryjeme metodu toString(), čímž umožníme správné zob­raz­ení v ListView.

  4. public class Zaznam {
        String nazev;
        String popis;
        boolean dulezite;
        
        public Zaznam(String nazev, String popis, boolean dulezite) {
            this.nazev = nazev;
            this.popis = popis;
            this.dulezite = false;
        }
        
        public String toString() {
            String vysledek = this.nazev + "\n" + this.popis.substring(0,25) + "...";
            if (this.dulezite) vysledek = "[!] " + vysledek;
            return vysledek;
        }
    }
    

  5. Nac­hystáme si třídu Data, která bude zastřešovat op­erace nad kon­tejnerem jedno­tlivých záznamů. Zároveň ji v dalších kapitolách doplníme tak, aby zasílala in­for­mace o změnách v záz­namech kom­ponentě ListView. K tomu použijeme im­plemen­taci návrhového vzoru Pozorovatel (ob­serv­er) v Javě. Třída Data zatím vytvoří několik záznamů „ručně“, později ji ale doplníme tak, aby data četla ze souboru.

  6. public class Data {
        ArrayList<Zaznam> data = new ArrayList<Zaznam>();
        protected void nactiData() {
            this.data = new ArrayList();
            Zaznam z;
            z = new Zaznam("Programování pro Android", "Tvorba aplikací pro Android.", true);
            this.data.add(z);
            z= new Zaznam("Hra na kytaru", "Základní akordy na kytaru.", false);
            this.data.add(z);
            z = new Zaznam("Terra Mystica", "Naučil jsem se novou deskovou hru.", false);
            this.data.add(z);
        }
        public ArrayList<Zaznam> getData() {
           return this.data;
        }
    }
    

  7. V hlavní ak­tivitě si vytvoříme in­stan­ci třídy Data a in­icializujeme ji.

  8. Ab­yc­hom mohli načíst jedno­tlivé záznamy do ListView, bude nejprve potřeba vytvořit datový adaptér, který se nám bude starat o data v ListView. Dále našemu sez­namu (ListView) tento adaptér přiřadíme.
    Celý kód hlavní ak­tiv­ity vypadá násle­dovně:

  9. public class MainActivity extends AppCompatActivity {
        Data zaznamy;
        ListView seznam;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            // Vytvoření a inicializace třídy Data:
            this.zaznamy = new Data();
            this.zaznamy.nactiData();
            
            // Vytvoření ListView a propojení s daty:
            this.seznam = (ListView) this.findViewById(R.id.lv_seznam);
            this.seznam.setAdapter(new ArrayAdapter<Zaznam>(this, android.R.layout.simple_list_item_1, this.zaznamy.getData()));
        }
    }
    

  10. V hlavní ak­tivitě také připravíme metodu aktualizuj(), která zařídí ak­tualizaci ob­sahu ListView při změně ob­sahu.
    K tomu použijeme metodu notifyDataSetChanged() adaptéru ArrayAdapter:

  11. private void aktualizuj() {
        ListView seznam = (ListView) this.findViewById(R.id.lv_seznam);
        ArrayAdapter<Zaznam> adapter = (ArrayAdapter<Zaznam>) seznam.getAdapter();
        adapter.notifyDataSetChanged();
    }
    

  12. Vyz­koušej­te funkčnost kom­ponen­ty ListView. Zatím nereaguje na volbu položky, ale položky by se již měly zob­razovat.

Pos­tup – rea­kce na volbu položky:

Položky tedy máme načtené v kom­ponentě ListView. Nyní budeme chtít zařídit, aby se při volbě položky změnila její důležitost. Pro­jeví se to změnou popisu položky.

  1. Připravíme metodu zvolenaPolozka() do hlavní ak­tiv­ity. Ta získá zvol­ený záznam z Li­stView a zavolá jeho metodu invertujDulezite() (tu jsme si již ve třídě Zaznam vytvořili):

  2. private void zvolenaPolozka(AdapterView<?> zvolenyView, View polozka, int poziceVAdapteru, long cisloRadkuUvnitrView) {
        Zaznam zaznam = (Zaznam) zvolenyView.getItemAtPosition(poziceVAdapteru);
        zaznam.invertujDulezite();
    }
    

  3. Přidáme v metodě onCreate() hlavní ak­tiv­ity položku ListView OnClickListener.
    Použijeme an­onymní li­sten­er, který zavolá metodu zvolenaPolozka():

  4. protected void onCreate(Bundle savedInstanceState) {
        ...
        this.seznam.setAdapter(new ArrayAdapter<Zaznam>(this, android.R.layout.simple_list_item_1, this.zaznamy.getData()));
        this.seznam.setOnItemClickListener(
            new AdapterView.OnItemClickListener() {
                    @Override
                    public void onItemClick(
                            AdapterView<?> zvolenyView, View polozka, 
                            int poziceVAdapteru, long cisloRadkuUvnitrView) {
                                zvolenaPolozka(zvolenyView, polozka, 
                                poziceVAdapteru, cisloRadkuUvnitrView);
            }
        });
    }
    

Shrnutí

  1. Uspořádání grafic­kých prvků v okně mají na staros­ti kom­ponen­ty souhrnně označované jako layouts.

  2. Dosud jsme používali především LinearLayout, který je vhodný pro jedno­duché uspořádání prvků pod sebou nebo vedle sebe.

  3. Pro složitější návrhy je vhodné použít RelativeLayout, nejlépe s grafic­kým návrhářem vzhledu – například tím, který je součástí vývojového prostředí An­droid Studio.

  4. V této kapitole jsme ukázali a vyz­koušeli si layout ListView, který je vhodný pro zob­raz­ení většího počtu položek, které se navíc mohou dynamic­ky měnit.

  5. Pro naplnění ListView položkami jsme použili ArrayAdapter, který bere položky z ArrayListu.

  6. Pokud se změní data v ArrayListu, in­for­mujeme ArrayAdapter metodou notifyDataSetChanged() a ten zaj­istí ak­tualizaci ListView.

  7. Rea­kci na volbu položky v ListView zaj­istí OnClickListener. Konkrétní zvolenou položku získáme metodou getItemAtPosition() třídy ListView.