Vyvíjíme pro Android: Bližší pohled na pohledy – 1. díl

Jak bychom mohli vyvíjet aplikace pro Android, kdybychom neuměli používat vestavěná View? Jak bychom tvořili uživatelské rozhraní, kdybychom neuměli pracovat s Layouty? V dnešním dvojčlánku si všechny důležité představíme a povíme si něco o jejich atributech a metodách.

Seriál: Vyvíjíme pro Android (14 dílů)

  1. Vyvíjíme pro Android: Začínáme 15.6.2012
  2. Vyvíjíme pro Android: První krůčky 22.6.2012
  3. Vyvíjíme pro Android: Suroviny, Intenty a jednotky 29.6.2012
  4. Vyvíjíme pro Android: Bližší pohled na pohledy – 1. díl 13.7.2012
  5. Vyvíjíme pro Android: Bližší pohled na pohledy – 2. díl 13.7.2012
  6. Vyvíjíme pro Android: Fragmenty a SQLite databáze 20.7.2012
  7. Vyvíjíme pro Android: Preference, menu a vlastní Adapter 27.7.2012
  8. Vyvíjíme pro Android: Intenty, intent filtry a permissions 3.8.2012
  9. Vyvíjíme pro Android: Content providery 10.8.2012
  10. Vyvíjíme pro Android: Dialogy a activity 17.8.2012
  11. Vyvíjíme pro Android: Stylování a design 24.8.2012
  12. Vyvíjíme pro Android: Notifikace, broadcast receivery a Internet 31.8.2012
  13. Vyvíjíme pro Android: Nahraváme aplikaci na Google Play Store 7.9.2012
  14. Vyvíjíme pro Android: Epilog 14.9.2012

V článku Vyvíjíme pro Android: Suroviny, Intenty a jednotky jsme pochopili, jak fungují resources, co jsou to modifikátory, zjistili jsme, co to je jednotka dp, naučili jsme se ukládat řetězce jako suroviny, seznámili jsme se s Intenty, upravovali jsme různá View za běhu; stručně řečeno, bylo toho hodně. Dnes si od úplně nových konceptů dáme téměř oddych a budeme si rozšiřovat znalosti věcí, o kterých již víme, že existují. Představíme si jednotlivá view.

Protože byl minulý týden svátek a žádný nový díl nevyšel, dnes jsem si pro vás připravil hned dvojdíl.

Jak vlastně view fungují?

Abychom je mohli používat, musíme vědět, co to ta View jsou, jak se liší od svého potomka ViewGroup, jaké mají tyto třídy metody a jak je používat.

View je základní stavební jednotkou uživatelského rozhraní. Textový popisek je view, stejně tak jako tlačítko, rozbalovací seznam anebo LinearLayout, který už známe a který určuje, jako budou jednotlivá view uspořádána. ViewGroup je velmi důležitá dceřiná třída View, která může obalovat jedno nebo více View (tedy i ViewGroup) a má na starosti jejich rozmístění a vykreslení. Jak může takový jednoduchý layoutový strom vypadat, je ukázáno v dokumentaci.

Stromy mohou být mnohem složitější, ale snažte se vždy o co největší jednoduchost, jakmile se začne struktura komplikovat, měli byste se zamyslet, zdali to nelze udělat jinak, zdali neexistuje nějaké už hotové view, které daný problém řeší elegantněji případně zdali si takové view nevytvořit. Také se snažte jednotlivé celistvé bloky uživatelského rozhraní vytvářet jako fragmenty.

Na následujících řádcích budu probírat jednotlivé potomky třídy View, ukážeme si, jak vypadají, popíšeme si důležité atributy a důležité funkce.

Ve druhém dílu jsem mluvil o konvencích, co se velikosti písmen, uzavírání do <code> týče atpod. Rozhodl jsem se to změnit. Do <code> budu uzavírat jen tehdy, když mluvím o konkrétní třídě, například že má nějaké metody nebo potomky ( View). Když mluvím o jejích instancích anebo potomcích, použiji variantu s velkým písmenem, ale ne strojopisem (View), a když mluvím o obecném vzoru, použiji malé počáteční písmeno (view). Není nijak důležité si to pamatovat, jen obhajuji, že ten zdánlivý chaos má (většinou) určitý řád.

View

Třída View je rodičem všech dalších tříd, o nichž si budeme dnes povídat. Implementuje všechny základní metody a atributy, a to jak „technické“ (jak se view vykreslí), tak „vzhledové“, tedy různé manipulace s tím daným view, měnění jeho atributů a tak dále. My se budeme věnovat jen „vzhledovým“ metodám.

Každé view je v podstatě jenom obdélník. View jako obdélník opravdu vypadá, ostatní třídy mohou vypadat lépe, ale to je zásluha šikovných tvůrců – kreslířů.

Takhle vypadá surové View – správně, není vidět.

Důležité a zajímavé atributy View
Jméno atributu Hodnoty Odpovídající funkce Popis
android:alpha Floatové číslo v rozmezí <0;1>. setAlpha, getAlpha 0 znamená úplnou průhlednost, jednička znamená neprůhlednost. Zadáte-li v Javě desetinné číslo, bere se jakou double, které se nenechá implicitně přetypovat na float. Je proto potřeba hodnoty alphy zapisovat s příponou f (např.  0.7f).
android:background Odkaz na jakoukoli drawable surovinu, color surovinu nebo hexadecimální zápis barvy ve formátu #rgb, #argb, #rrggbb, #aarrggbb, kde a je volitelná alpha. setBackgroundResource, setBackgroundColor a odpovídající gettery Použijete-li jako hodnotu barvu, doporučuji umístit ji do /res/values/colors.xml a jako id ji dát její název. Ohromně to usnadní zachovávání barevné konzistence napříč projektem, když chcete trochu změnit odstín červené, uděláte to jen jednou. Navíc jsou i hodnoty atributů popisnější, místo #80000080 tam bude  @color/half_opaque_dark_red.
android:id Buď existující id ( @id/foo), anebo nově vytvořené ( @+id/bar). setId a odpovídající getter. Id nemusí být unikátní, je však vhodné, je-li unikátní alespoň v rámci podstromu, v němž budete vyhledávat metodou View.findViewById() či Activity.findViewById(). Doporučuji, aby se v každém layoutovém XML souboru vyskytlo každé id maximálně jednou. Pokud by se totiž uvnitř jednoho RelativeLayoutu (povíme si o kousek níže) sešlo více stejných id a shodou okolností by měl některý prvek pozici závislou na prvku s nejedinečným id, došlo by k problémům. Když není id unikátní, měl by se vrátit první nález.
android:padding Jakákoli délková jednotka setPadding,
getPaddingLeft,
getPaddingTop,
getPaddingRight
getPaddingBottom
Nastavuje vnitřní okraje daného view. To tedy zabere tak velký obdélník, jaký specifikujeme rozměrovými atributy android:layout_height resp. android:layout_width, ale případný obsah daného view (třeba text u EditTextu) bude „odsazený” o daný padding. V případě atributu android:padding je padding ze všech čtyř stran stejný, chcete-li nastavit různé hodnoty, použijte android:paddingLeft a analogicky i android:paddingTop  atd.
android:visibility visible,
invisible,
gone
setVisibility, getVisibility Atribut android:visibility funguje úplně stejně jako visibility v CSS. visible je analogické s visibility:visible, view je vidět. invisible je to stejné jako visibility:hidden, kdy view sice není vidět, ale zabírá místo, které by zabíralo, kdyby vidět bylo. gone je to samé jako display:none, view není vidět a ani nezabírá žádné místo. Hodí se ještě upozornit, že příslušné metody přijímají resp. vracejí číslo z množiny {0, 4, 8}, které odpovídá konstantám View.VISIBLE, View.INVISIBLE a View.GONE. Pozn.: trochu zmatečné je, že samotné hodnoty atributu android:visibility se při kompilaci také překládají na čísla, ale na 0, 1 a 2. To nás však trápit nemusí.

Pokud není řečeno jinak, atributy, stejně tak jako později metody a vlastnosti, jsou v Androidu od API 4 nebo dříve. Atributy jsou seřazeny abecedně. Zde jsem vybral jen ty zajímavé či důležité, pro další vás odkážu na seznam všech atributů.

Šikovné metody
Metoda Popis
View findViewById(int id) Tuto metodu jsme si už představili a několikrát použili, dnes jsem o tom psal už u android:id. Nemá ji samozřejmě valný smysl volat na ne-potomcích ViewGroup (tehdy vrátí vždy null), ale pro zjednodušení je definována na  View
View findViewWithTag(Object tag) Funguje stejně jako findViewById, až na to, že místo id hledá podle tagu. Hledá pouze tagy bez klíče (viz setTag).
Context getContext() Vrátí objekt Context, v rámci nějž dané view existuje. Hodí se ve všech případech, kdy máte pouze view, ale potřebujete použít metodu, jež vyžaduje  Context.
View getRootView() Vrátí kořenové view. V případě, že dané view žádného rodiče nemá, tedy je buď samo kořenovým view, anebo bylo například právě vytvořeno a ještě jsme ho nikam neumístili, vrátí samo sebe.
Object setTag(int key, Object tag) Vždy, když potřebujete k view nějakým způsobem „připíchnout” nějaká další data, použijte tuto metodu. Jen upozorňuji, že klíčem musí být nějaké id specifikované v resources (viz Resources – IDs). Uložený objekt potom získáte pomocí Object getTag(int key). Metoda Object setTag(int key, Object tag) má i variantu bez klíče, Object setTag(Object tag). Ta danému view nastaví „hlavní” tag, pomocí něhož a metody findViewWithTag lze potom to view i nalézt. Existuje i xml atribut android:tag, který je analogický k tagovým metodám bez klíče, ale v přehledu atributů jsem ho neuváděl, neboť jsem se zatím nedostal do situace, kdy by se hodil. Chcete-li v XML označit view, aby se s ním později v kódu dalo pracovat, použijte určitě radši id.

Zde jsem uvedl jen pár metod. Pro seznam všech vás odkážu na dokumentaci.

V přehledu metod jsem nezmínil jednu rodinu velmi důležitých metod, a to ty, které nastavují posluchače událostí. Všechny mají signaturu typu setOnFooListener(View.OnFooListener), kde si za foo dosaďte například click nebo drag. Každá přebírá jako parametr objekt implementující dané rozhraní, které obsahuje jednu metodu. Ta metoda má jako parametr určitě View, na němž daná událost proběhla, a v případech, kdy je to smysluplné, nějaká další data. Základní události definuje přímo View, další události si mohou definovat jeho potomci. U View  si ukážeme, jak pracovat s nejjednodušší událostí, tedy tapnutím, ostatní důležité události jen zmíním.

Metoda setOnClickListener(View.OnClickListener l) nastaví objektu listener pro klepnutí, tapnutí nebo jak to chcete nazývat. Rozhraní View.OnClickListener má jednu metodu, a to onClick(View v), kde View v je to view, na které uživatel klepnul.

Teď vám ještě nabídnu tabulku dalších důležitých událostí, potom si některé metody a atributy vyzkoušíme v praxi a tím budeme mít View probrané.

Běžně používané události
Setter Metody rozhraní Popis
setOnClickListener(
View.OnClickListener l)
onClick(View v) Tato událost je zavolána při klepnutí na view. Parametr metody onClick je to view, na které bylo klepnuto.
setOnDragListener(
View.OnDragListener l)
boolean onDrag(View v, DragEvent event) Tato metoda je součástí Drag and Drop API, které je v Androidu až od API 11. Mně drag and drop na Androidu přijde jako nepříliš příjemné, myslím si, že se vždy dá nahradit přirozenějším Action Barem. Drag and dropu se věnovat neplánuji, o Action Baru uvažuji. Vraťte true, pakliže jste danou událost zpracovali, jinak  false.
setOnKeyListener(
View.OnKeyListener l)
boolean onKey(View v, int keyCode, KeyEvent event) Dokumentace tvrdí, že je to metoda hlavně pro hardwarové klávesnice, že u softwarových nelze s jistotou říci, zda tuto událost spustí, nicméně například u EditTextu to funguje bezpečně. keyCode obsahuje kód klávesy. Ten se dá získat i z objektu KeyEvent, který navíc obsahuje i informaci o tom, jestli byla klávesa stisknuta či uvolněna. Také jsou v něm definovány konstanty pro jednotlivé klávesy, takže i při práci s keyCode  nemusíte do kódu zanášet nicneříkající čísla. onKey musí vrátit boolean, zda danou událost „zkonzumovala”, či nikoli. Za zmínku ještě stojí, že při implementaci Activity můžete vytvořit metody onKeyDown, onKeyUp atp., které fungují velmi podobně jako OnKeyListener. Díky nim můžete navíc implementovat například vlastní posluchač stisknutí tlačítka zpět (například hra se při stisknutí tlačítka zpět pozastaví a uživateli nabídne nějaké menu). Nezneužívejte to však, tlačítko home takto stejně přepsat nejde; akorát naštvete uživatele.
setOnLongClickListener(
View.OnLongClickListener l)
boolean onLongClick(View v) Totéž jako onClickListener, akorát se volá tehdy, když uživatel dané view chvíli podrží. Vraťte true, pakliže jste danou událost zpracovali, jinak  false.

A co nějaký kód?

Do /res/values/colors.xml jsme přidali následující řádky (poté, co jsme klikli pravým tlačítkem na složku values a potom vybrali  New → Other... → Android → Android XML Values File):

<color name="green">#0f0</color>
<color name="blue">#00f</color>

/res/layout/view.xml potom vypadá stejně, jako následující řádky. Se znalostmi z tohoto a předchozích dílů byste jim měli bez problému porozumět, a tak je záměrně nechám bez komentáře.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <View
        android:id="@+id/red_view"
        android:tag="first_view"
        android:background="#f00"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:onClick="hideView" />

    <View
        android:background="@color/green"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:paddingLeft="15dp"
        android:paddingBottom="30dp" />

    <View
        android:id="@+id/blue_view"
        android:background="@color/blue"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

</LinearLayout>

Ani ViewActivity.java není s dnes načerpanými znalostmi nijak složitá na pochopení, nicméně nějaké komentáře jsem pro jistotu přidal.

public class ViewActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.view);

        View v = new View(this); // Vytvoření View v kódu

        View red = findViewById(R.id.red_view);
        View blue = findViewById(R.id.blue_view);

        blue.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                // Nepříjímá argument double, tak pomocí přípony f řekneme, že
                // číslo je float
                v.setAlpha(0.5f);
            }
        });

        blue.setTag(red); // Nastavení nějakého objektu (zde View) jako tagu
        View root = blue.getRootView();
        Toast.makeText(this, String.valueOf(blue == root.findViewWithTag(red)),
                Toast.LENGTH_SHORT).show(); // true

        Toast.makeText(this,
                String.valueOf(red == root.findViewWithTag("first_view")),
                Toast.LENGTH_SHORT).show(); // true
    }

    public void hideView(View v) {
        v.setVisibility(View.GONE);
    }
}

Takhle vypadá ViewActivity 

TextView

TextView, přímý potomek View, slouží k zobrazování textu. Samo o sobě umí „jen” zobrazit text, ale mezi jeho potomky patří mnoho dalších důležitých view, jako například EditText či Button. U TextView  ukážu jen opravdu základní metody a atributy, nebudu se věnovat ani těm, které z něj mohou udělat EditText, ani těm, které jsou sice zajímavé, ale ne kriticky důležité (např. android:ellipsize či android:ems), abych vás zbytečně nezahltil. V případě potřeby je určitě najdete v dokumentaci. Některé atributy a metody TextView uvedu až u EditTextu, přestože tam ve skutečnosti nepatří, ale rámcově se tam hodí víc a budete je používat téměř pouze právě s ním.

Důležité a zajímavé atributy TextView
Jméno atributu Hodnoty Odpovídající funkce Popis
android:autoLink none,
web,
email,
phone,
map,
all,
či kombinace oddělené svislítkem (|)
setAutoLinkMask(int mask) Stejně jako u visibility  jsou jednotlivé konstanty překládány na čísla (ale tentokrát mají konstanty pro metodu stejné hodnoty jako konstanty pro atribut, viz odkázaná dokumentace). Svislítko je bitová operace nebo a hodnota all je vlastně zkratka pro web|email|phone|map. Map by mělo zvýrazňovat adresy a nabídnout odkaz na nějakou mapu, nikdy jsem to nepoužil a nejsem si jist, jak to funguje v českém prostředí. Ostatní tři hodnoty jsou ale velmi šikovné. Každý, koho napadla otázka „A jak to funguje pro EditText?” je správně zvídavý. Díky zkoušení různých neobvyklých kombinací se člověk často dostane k věcem, které mu potom budou každou chvíli usnadňovat život. A i kdyby ne, procvičí se v programování.
android:gravity left,
right,
center_horizontal
a mnoho dalších.
Možno kombinovat svislítkem.
setGravity(int mask) Podobné, jako CSS text-align (nastaví zarovnání textu doleva, na střed či doprava), nicméně android:gravity má mnohem více množných hodnot. Zde jsem uvedl jen ty základní, které podlě mě použijete v naprosté většině případů. Jen upozorním, že použijete-li například center_horizontal, centrujete text v rámci TextView, takže jestli nastavíte šířku na wrap_content, žádnou změnu gravitace nezpozorujete.
android:lines Číslo či odkaz na číslo v resources setLines Abyste nemuseli složitě počítat přesnou výšku TextView tak, aby za dané velikosti textu (a danými vzdálenostmi mezi řádky, které se mimochodem dají také nastavit) odpovídala n řádkům, můžete použít atribut android:lines, jehož hodnota je přesná výška v řádcích. Asi vás nepřekvapí, že existuje i android:maxLines  a android:minLines. Abychom předešli špatně odhalitelným problémům s kombinací android:lines a android:layout_height, řekneme si rovnou, že lines nastavují velikost TextView, zatímco layout_height nastavuje velikost obdélníku, do kterého se TextView vykreslí. Pokud layout_height nastavíte na wrap_content, není problém. Pokud ho ale nastavíte na nějakou fixní hodnotu (včetně match_parent), jakmile výška v řádcích přesáhne layout_height, TextView se ořízne.
android:text Řetězec (fůůj) anebo identifikátor řetězcové suroviny setText (má mnoho různých signatur) Tady není moc co říkat, atribut android:text prostě nastaví obsah TextView.
android:textAppearance Odkaz na style resource anebo theme atribut Tohle je trochu složitější a plně si to vysvětlíme ve článku o stylování. Kvůli komplikovanosti jsem i vynechal analogickou metodu. Prozatím budeme používat pouze theme atributy, což jsou také jakési identifikátory, akorát místo zavináčem začínají otazníkem a odkazují na skupinu vzhledových vlastností nastavenou v aktivním motivu (theme). Při použití analogie s webovým vývojem se dá říci, že motiv je něco jako CSS stylopis a theme atribut je něco jako třída. Samotný Android framework nabízí několik motivů, díky nimž můžete jedním řádkem nastavit, jak bude aplikace vypadat, zda bude mít bílý text na černém pozadí, černý text na bílém pozadí atp. A tyto motivy definují i theme atributy, tedy jakési třídy vlastností, které spolu nějak souvisí. Pro zjednodušení si teď ukážeme jen tři, a to ?android:attr/textAppearanceSmall, ?android:attr/textAppearanceMedium a ?android:attr/textAppearanceLarge. Upřímně řečeno, zatím jsem snad nikdy ani nepotřeboval nic jiného.
android:textColor To samé co do android:background atributu setTextColor Nastavení barvy textu.

 První TextView ukazuje možnosti autoLink , druhý má velký text a je zarovnán doleva, třetí má prostřední text a zarovnán na střed a čtvrtý má malý text a je zarovnán vpravo.

Užitečné metody třídy TextView
Signatura Popis
int getLineCount() Vrátí počet řádků, které zabírá text v daném TextView. Musí se ale zavolat až poté, co to TextView získá layout – hodně zjednodušeně poté, co je vykresleno. Zavoláte-li tuto metodu třeba v onCreate, s nejvyšší pravděpodobností vrátí vždycky 0, protože view ještě úplně vykreslena nebyla.
CharSequence getText() Vrátí text. Na String ho převedete zavoláním metody  toString().
int length() Vrátí délku textu ve znacích.
void setError(CharSequence popupText) Vpravo vedle TextView se zobrazí obrázek naznačující chybu. Pozor na to, že text předaný této metodě se nenastaví jako text, ale měl by se zobrazit tehdy, když TextView obdrží focus. Vzhledem k tomu, že s tím to je poměrně komplikované a je rozdíl, jestli má telefon hardwarové ovládání, anebo ne, nedoporučuju se na tenhle text spoléhat. Pokud jako parametr předáte null, ikonka zmizí.

Takhle nějak se na Androidu ICS zobrazí TextView po zavolánísetError .

   

Pro mnoho dalších atributů a metod TextView se podívejte do jeho dokumentace. Jen pozor na to, abyste kvůli tomu všemu nezapomněli, že existují speciální třídy, mnohem vhodnější pro některé úkony, jako například EditText, Button, CheckBox nebo RadioButton. My si z nich povíme něco jen o  EditText u, Button nemá oproti TextView nic navíc, takže s ním vlastě už umíme, a ty ostatní dvě jsou také už velmi jednoduchá, takže si je, až je budete potřebovat, můžete nastudovat bez problému sami. Pěkně jsou různá formulářová view rozebrána v Input Controls.

Programujeme

Všimněte si, že jsem u <TextView>  s id large_text nastavil nejen atribut android:onClick (abych mohl ukázat, že metoda getLines vrací správné výsledky), ale musel jsem mu nastavit i android:clickable, aby se na něj dalo tapnout. View má ve výchozím stavu clickable="true", ale u TextView  tomu tak není.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:autoLink="all"
        android:text="@string/auto_link" />

    <TextView
        android:id="@+id/large_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="true"
        android:gravity="left"
        android:onClick="compareLineCount"
        android:text="@string/seo_text"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
        android:id="@+id/medium_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:text="@string/seo_text"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/small_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="right"
        android:text="@string/seo_text"
        android:textAppearance="?android:attr/textAppearanceSmall" />

    <TextView
        android:id="@+id/error"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/error" />

</LinearLayout>

K TextViewActivity.java  nic dodávat nemusím.

public class TextViewActivity extends Activity {

    TextView large;
    TextView medium;
    TextView small;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.text_view);

        ((TextView) findViewById(R.id.error)).setError("Někde se stala chyba.");

        large = (TextView) findViewById(R.id.large_text);
        medium = (TextView) findViewById(R.id.medium_text);
        small = (TextView) findViewById(R.id.small_text);

        Toast.makeText( // true, všechny tři mají stejný text
                this,
                String.valueOf(large.length() == medium.length()
                        && large.length() == small.length()),
                Toast.LENGTH_SHORT).show();
    }

    public void compareLineCount(View v){
        Toast.makeText( // skoro určitě false, ale na širokých displejích možná i true
                this,
                String.valueOf(large.getLineCount() == small.getLineCount()),
                Toast.LENGTH_SHORT).show();
    }
}

EditText

EditText je asi nejčastěji používaným formulářovým view na Androidu. Jako u všech ostatních view si i u něj představíme některé atributy a metody, nicméně v tomto případě jsou všechny bez výjimky ve skutečnosti definované už na TextView, ale používat je budeme na EditTextech, a tak mi přijde logičtější zařadit je sem. Ještě než začnu, upozorním na jednu zajímavou podtřídu EditText u, a to AutoCompleteTextView, což je EditText s funkcí autocomplete.

Důležité a zajímavé atributy EditText
Jméno atributu Hodnoty Odpovídající funkce Popis
android:capitalize none,
sentences,
words,
characters
Určuje, která písmena (v závislosti na postavení v textu) se mají automaticky převádět na velká. Výchozí je none, které nepřevádí níc. sentences zvětšuje první písmena vět, words první písmeno každého slovo a characters zvětší každý znak. Já osobně nedoporučuji používat nic kromě characters (pokud máte důvod, že chcete mít celý text velkými písmeny), neboť jednak klávesnice automatické opravy a zvětšování písmen mívají (a uživatel je může vypnout) a jednak nemůže být algoritmus přesný, takže občas uživatele strašně otráví (velké písmeno po každé tečce, nemožnost napsat John von Neumann apod.).)
android:hint Řetězcová surovina nebo řetězec setHint Nastaví řetězec, který se na daném EditTextu bude zobrazovat tehdy, když v něm není nic napsaného. Obdoba placeholderu z HTML5, až na to, že narozdíl od některých (všech?) implementací HTML5, v Androidu zůstane i poté, co EditText získá focus, ale nic v něm ještě není napsáno (což je určitě uživatelsky příjemnější chování). Nastavením hint u nikdy nic nezkazíte.
android:inputType none,
text,
textEmailAddress,
textPassword,
number,
phone a mnoho dalších.
Možno kombinovat svislítkem (|).
setRawInputType(int type) Tohle je ohromně důležitý atribut. Nejenže validuje zadaná data, ale navíc se mu umí přizpůsobit softwarové klávesnice. Pokud například nastavíte textEmailAddress, mohou místo například čárky zobrazit zavináč. Pokud nastavíte number, klávesnice zobrazí číselník. Pokud je typem vstupu textPassword či numericPassword, místo znaků se budou zobrazovat hvězdičky. Atribut android:inputType se objevil v API 3, předtím měla TextView mnoho různých pravdivostních atributů typu android:numeric, android:password atp. Ty jsou teď deprecated.
android:enabled true,
false
setEnabled Tento atribut specifikuje, zda jsou povoleny interakce s view, v případě EditTextu určuje, jestli se do něj dá psát. Asi jste si všimli, že atribut android:enabled (mimochodem definovaný pro všechny potomky TextView) není odkazem. Je to proto, že se o něm API reference vůbec nezmiňuje. Když ho použijete v Eclipse, tvrdí vám, že je deprecated a že máte použít state_enabled, který neexistuje. Proč se o něm API reference nezmiňuje, to nevím, ale chyba v Eclipse je dána chybou v androidích zdrojácích. Podíváte-li se na {vaše složka s ADK}/platforms/android-15/data/res/values/attrs.xml najdete tam řádek, který je potomkem definice atributů pro TextView, který zmiňuje android:enabled, ale tvrdí, že je deprecated ve prospěch neexistujícího atributu. Myslím si, že tato chyba bude opravena a že atribut enabled bude vzat na milost. Do té doby se buď smiřte s tím, že používáte něco, co formálně neexistuje, anebo použijte příslušnou metodu.

Metody žádné zmiňovat nebudu, takže si můžeme ukázat nějaký kód.

Ukázka kódu

Tentokrát jen layoutový XML soubor, Activity spočívá v tom, že nastaví správně contentView. Popisky a text jsem ošklivě napral přímo do příslušných atributů, neboť vysvětlují, co který EditText umí.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:enabled="false"
        android:text="Zakázaný vstup" />

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Sem vepište jakýkoliv text"
        android:inputType="text" />

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Kterékoli reálné číslo"
        android:inputType="numberSigned|numberDecimal" /> <!-- Samozřejmě jako desetinný rozvoj. -->

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Textové heslo"
        android:inputType="textPassword" />

</LinearLayout>

Takhle vypadají EditTexty na Androidu ICS. 

  

ImageView

ImageView, jak název napovídá, dokáže zobrazit obrázek. Protože není tak důležité, pouze se zmíním, že existuje, ukážu úplný základ a zájemce odkážu na Working With Images In Android, což je článek, který opravdu polopaticky vysvětluje práci s ImageView  o něco více do hloubky. Obrázky se však na telefonech zase tak moc nepoužívají (a tam, kde ano, obvykle nabízí Android jednodušší řešení) a pro většinu výjimek vám bude stačit to, co teď ukážu.

Dá se říci, že na displejích mdpi velmi zhruba jeden reálný pixel odpovídá (tzn. s odchylkou do asi 20 %) jednomu dp. Z toho můžete vycházet v tom, jak veliký obrázek pro mdpi displej vyrobíte. Potom ho musíte ještě samozřejmě velikostně předělat pro ostatní hustoty, viz kapitola Jednotky v minulém článku. Když máte v /res/drawable-ldpi, /res/drawable-mdpi, /res/drawable-hdpi a /res/drawable-xhdpi stejně pojmenované obrázky lišící se pouze velikostí, je potom už jednoduché ImageView použít:

<ImageView
    android:src="@drawable/image"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

ProgressBar

ProgressBar je nástroj, kterým dáme uživateli vědět, že se něco děje, přestože to nevidí. Je to taková obdoba technologické přestávky na silnicích. Atributy tentokráte neseřadím podle abecedy, ale přirozeněji, od jednodušších ke složitějším (respektive od základních ke specifičtějším).

Některé atributy
Jméno atributu Hodnoty Odpovídající funkce Popis
android:indeterminate pravdivostní hodnota setIndeterminate Nastavuje, zda je progressbar konečný, či nekonečný. Jak později zjistíme, až budeme kódovat, tento atribut má smysl pouze u (vestavěného) horizontálního ProgressBar, kolečko se jako nekonečné chová vždy. Takže pro úplně nejjednodušší případ stačí nakódit element <ProgressBar>, nastavit mu layout_width, layout_height a tím máme vystaráno.
android:progress Přirozené číslo nebo nula (ne vyšší než maximální nastavená hodnota, viz o dva řádky tabulky níže). setProgress Počáteční pokrok. Není-li nastaven, má hodnotu 0. Má smysl jen tehdy, umí-li ProgressBar nějaký pokrok zobrazit, tedy u android:indeterminate="false"  na horizontálním ProgressBaru.
style Odkaz na style resource style je úplně zvláštní atribut, mimojiné tím, že se před něj nepíše namespace android. Pořádně si o něm povíme, až budeme mluvit o stylování, proteď nám stačí jen si říct, jakých hodnot může v tomto případě nabývat. @android:style/Widget.ProgressBar je výchozí vzhled ProgressBaru, ostatní ( @android:style/Widget.ProgressBar ,
@android:style/Widget.ProgressBar.Horizontal ,
@android:style/Widget.ProgressBar.Small ,
@android:style/Widget.ProgressBar.Large ) mluví za sebe. Jednu věc si ale už zmínit neodpustím, a to je to android: bezprostředně za zavináčem. Ve skutečnosti mají i zdroje namespace, a to je právě ta část hned za zavináčem, od zbytku oddělená dvojtečkou. Pokud ho neuvedete, automaticky se berou suroviny té které aplikace. Ale často budete používat právě namespace android, který združuje všechny vestavěné suroviny. Budete ho používat právě při stylování (budete dědit od vestavěných stylů). Aby správně fungovaly některé věci, budete muset svému view nastavit id specifikované už v Android frameworku. A tak dále.
android:max Jakékoli přirozené číslo setMax Potřebujete konečný progressbar? A nevyhovuje vám rozsah 0– max? Tímto atributem si můžete nastavit vlastní maximální hodnotu. Například když zobrazujete postup stahování souboru, jako max můžete nastavit jeho velikost.

Vedle výše jmenovaných nás zajímá už jen jedna metoda, a to incrementProgressBy(int diff), která místo nastavení nové hodnoty progressu jeho hodnotu zvýší o  diff.

Kdyby někdo chtěl vyzkoušet zvyšovat hodnotu progressu postupně, předpokládám, že by začal tak, že by do onCreate přidal nějaké takové řádky:

ProgressBar pb = (ProgressBar)findViewById(R.id.determinate_progressbar);
for(int i = 0;i<=100;i++){
    pb.setProgress(i);
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
    }
}

Tehdy by zjistil, že se mu jeho activity spouští dlouho, a když se spustí, je progressbar už plný. Tak by to vylepšil a zkusil by to odložit a spustit až po klepnutí na nějaké tlačítko s  android:onClick="startPretendingWork":

public void startPretendingWork(View v){
    final ProgressBar pb = (ProgressBar)findViewById(R.id.determinate_progressbar);
    for(int i = 0;i<=100;i++){
        pb.setProgress(i); // Jako pb.incrementProgressBy(1), je-li předtím progress 0
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
    }
}

Po kliknutí na tlačítko by se activity zasekla, a tak by zkusil spustit nové vlákno a uspávat to:

public void startPretendingWork(View v) {
    final ProgressBar pb = (ProgressBar)findViewById(R.id.determinate_progressbar);
    new Thread(new Runnable() {
        public void run() {
            for (int i = 0; i <= 100; i++) {
                pb.setProgress(i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
            }
        }
    }).start();
}

A to by fungovalo, on by si to zapamatoval a příště, když by chtěl třeba zobrazit nějaký text získaný z webu, by se divil, proč to nefunguje, a hledal by chyby všude možně, jen ne tam, kde chyba opravdu je.

V Androidu totiž existují dva typy vláken: Takzvané UI vlákno a pak vlánka ostatní. V UI vlákně běží celé uživatelské rozhraní, spouštějí se tam activity a tak dále. A má dvě pravidla:

  1. Nikdy neblokuj UI vlákno.
  2. Nikdy nepřistupuj k prvkům uživatelského rozhraní odjinud, než z UI vlákna.

Je tomu tak proto, že androidí UI není thread-safe. Některé metody třídy ProgressBar jsou výjimkou, o čemž napovídá to, že jsou deklarovány jako synchronized. Pokud byste ale stejným způsobem třeba měnili text nějakého TextView, obdrželi byste android.view.ViewRoot$CalledFromWrongThreadException. Jak pracovat ve vláknech si určitě někdy povíme, pokud to potřebujete hned, projděte si Processes and Threads.

Jak se tedy takový progressbar tvoří?

Opět vynecháme activity, která sestává jen z nastavení správného contentView, a podíváme se jen na layoutový XML soubor. Ten není totožný s tím, který bude v ukázkových zdrojácích a ze kterého pochází screenshot – zde jsem pro stručnost nahradil všechna TextView komentářem:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <!-- Nejjednodušší případ -->
    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <!-- Výchozí, nekonečný -->
    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminate="true" />

    <!-- Horizontální nekonečný -->
    <ProgressBar
        style="@android:style/Widget.ProgressBar.Horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminate="true" />

    <!-- Malý, nekonečný -->
    <ProgressBar
        style="@android:style/Widget.ProgressBar.Small"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminate="true" />

    <!-- Velký, nekonečný -->
    <ProgressBar
        style="@android:style/Widget.ProgressBar.Large"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminate="true" />

    <!-- Výchozí, konečný -->
    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminate="false"
        android:progress="45" />

    <!-- Horizontální, konečný -->
    <ProgressBar
        android:id="@+id/determinate_progressbar"
        style="@android:style/Widget.ProgressBar.Horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminate="false"
        android:progress="79" />

</LinearLayout>

Text opět nesprávně předávám hodnotou, ale i v tomto případě plní text popisku dokumentační funkci. Na následujícím obrázku si jistě všimnete, že nejjednodušší, výchozí-nekonečný a výchozí-konečný vypadají stejně.

Takhle se zobrazí layout, který jsme právě vytvořili.

   

Tím jsme sice zdaleka neskončili, ale máme za sebou všechny potomky View a zároveň ne-potomky ViewGroup, které si dnes ukážeme. View má samozřejmě mnohem více potomků, stačí se podívat do dokumentace, kde jsou vypsáni všichni přímí i nepřímí potomci zobrazit.

A protože jsme probrali všechny ne-potomky ViewGroup , myslím, že můžeme říci, že jsme dokončili kapitolu. Článek pokračuje ve Vyvíjíme pro Android: Bližší pohled na pohledy – 2. díl.

Matěj začal programovat ve třinácti v PHP, pak v JavaScriptu a Lispu. Nakonec si koupil Androida, a tak programuje hlavně pro něj.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 9

Přehled komentářů

Kuba Zásek
Matěj Konečný Re: Zásek
Kuba Re: Zásek
Jailmando Chyba při spouštění aplikace
Matěj Konečný Re: Chyba při spouštění aplikace
vaca new view se nezobrazi
vaca public void hideView(View v)
Petr setAlpha
Ievgen Re: setAlpha
Zdroj: https://www.zdrojak.cz/?p=3681