Přejít k navigační liště

Zdroják » Mobilní vývoj » Vyvíjíme pro Android: Suroviny, Intenty a jednotky

Vyvíjíme pro Android: Suroviny, Intenty a jednotky

V dnešním článku, který je součástí seriálu Vyvíjíme pro Android, si povíme něco o délkových jednotkách v Androidu, naučíme se pracovat s resources, naučíme se spouštět activity a pracovat s view.

minulém článku jsme si vytvořili aplikaci o jedné activity a dvou view. Dnes přidáme další activity, řetězce správně umístíme do resources, o nichž si toho plno povíme, a nakoukneme pod kapotu toho, jak Android počítá délkové jednotky a jak je používat tak, aby naše aplikace fungovala na zařízeních s různým DPI.

Suroviny

Přemýšleli jste, jak by se dal do aplikace vložit obrázek? Kam dát nějakou písničku? Jak na lokalizaci textu do různých jazyků? Nebo jak nabídnout zařízením s různou velikostí displeje různé layouty? Resources jsou správná odpověď.

Jak už jsem se ptal v prvním dílu a pak se to trochu řešilo v komentářích, mám trochu problém s překladem slova resources. Nakonec jsem se rozhodl pro český značně nepřesný ekvivalent suroviny, ale to jen z toho důvodu, že nemám rád zbytečná nesklonná přeložitelná anglická slova v českých textech. Občas použiji i výraz resources, a to v případě, kdy budu mít pocit, že je potřeba termín. Překlad suroviny je tu čistě z pocitových důvodů (všechny resources vs. všechny suroviny – co zní lépe?), nesnažím se zavést nějaký nový termín.

Všechny suroviny jsou uloženy ve složce res, a to mimo jiné v následujících podsložkách:

drawable/ obsahuje bitmapové obrázky (.png, .jpg, .gif) nebo speciální XML soubory, které si Android umí převést na obrázek. Vytvořený identifikátor má formát R.drawable.nazev_souboru_bez_koncovky. Názvy souborů tedy musí být unikátní.
layout/ obsahuje layoutové soubory, s nimiž jsme se už seznámili. Vytvořený identifikátor má formát R.layout.nazev_souboru_bez_koncovky. Názvy souborů tedy musí být unikátní.
menu/ obsahuje xml soubory s definicí různých menu. S nimi se v některém z příštích dílů seznámíme. Vytvořený identifikátor má formát R.menu.nazev_souboru_bez_koncovky. Názvy souborů tedy musí být unikátní.
raw/ obsahuje, jak již název napovídá, jakékoli soubory. Ty můžeme číst například pomocí InputStream. Vytvořený identifikátor má formát R.raw.nazev_souboru_bez_koncovky. Názvy souborů tedy musí být unikátní.
values/ obsahuje určité XML soubory ve speciálním formátu s různými hodnotami používanými skrz Android. Za chvíli se tomu budeme věnovat podrobně.
xml/ obsahuje XML soubory, čitelné pomocí metody Resources.getXML(R.xml.nazev_souboru_bez_koncovky). Názvy souborů tedy musí být unikátní. Pokud chcete používat XML konfiguraci, tak se soubory šup sem.

Co je to ta záhadná složka values?

Výborná otázka. Složka values obsahuje jednoduché suroviny, jako jsou řetězce, čísla nebo třeba styly. Na rozdíl od ostatních složek zde nejsou důležité názvy souborů. Dokonce můžete mít jeden jediný soubor a v něm namixovaná všechna data. Všeobecně uznávaná konvence je však data separovat a soubory pojmenovat podle typu obsažených dat, tzn. strings.xml, styles.xml nebo  arrays.xml.

Každý obsažený soubor má kořenový element <resources>, který může mít potomky jako <string>, <color> atp., které definují typ dané hodnoty. Podle toho se také vytvoří identifikátor. Pro řetězec to bude v R.string, pro barvu v R.color. Každý potomek elementu <resources> má atribut name, jehož hodnota určuje poslední část identifikátoru. Když budu mluvit o id nebo o identifikátoru u řetězcové suroviny, mám na mysli právě hodnotu atributu name. Řetězec s atributem  name="my_string" bude tedy mít identifikátor R.string.my_string. Způsob zadání hodnoty té určité suroviny s daným identifikátorem se liší, většinou jsou to tagy párové (platí pro např. <string> nebo <color>) a hodnotou je tedy to mezi těmi dvěma tagy, jindy jde o tag nepárový nebo naopak komplikovanější s mnoha potomky. Nás budou dlouho zajímat jen řetězce.

Jak vypadá takový soubor s řetězci?

<?xml version="1.0" encoding="utf-8"?>
<!-- /res/values/strings.xml -->
<resources>
    <string name="login_label">Login:</string> <!-- R.string.login_label -->
    <string name="password_label">Heslo:</string> <!-- R.string.password_label a ostatní analogicky-->
    <string name="submit_label">Přihlásit</string>
    <string name="no_menu_available">Není dostupný žádný jídelníček.</string>
    <string name="password_not_filled">Musíš zadat heslo.</string>
</resources>

Dobrá zpráva je, že ADT plugin pro Eclipse nabízí možnost zadávat všechny suroviny ve složce values pomocí různých formulářů, špatná zpráva je, že u jednoduchých hodnot typu řetězec je to nepohodlné a u složitých (styl) je to prakticky neproveditelné.

Modifikátory aneb kde začíná legrace

Slíbil jsem vám lokalizaci, různé layouty pro různá zařízení atd. a také to splním. Tohle všechno totiž dělají nenápadné a velice jednoduché modifikátory (v angličtině to nazývají qualifiers).

Takový modifikátor, to je vlastně jen pomlčkou oddělený speciální řetězec přidaný za název určité podsložky res. Modifikátorů je mnoho typů, my si zde ukážeme ty nejčastěji používané.

Typ modifikátoru Hodnoty Popis
Jazyk a region cs,
en,
fr,
en-rUS,
en-rGB
První část udává kód jazyku, druhá (nepovinná) s prefixem r označuje region.
Dostupná šířka w<N>dp
(např. w720dp,
w480dp)
Dostupná šířka se mění při změně orientace zařízení, dáte-li tedy telefon či tablet na šířku (landscape mode), dostupná šířka se oproti postavení na výšku (portrait mode) zvětší. Tento modifikátor budeme používat při tvorbě tabletových layoutů. To dp je rozměrová jednotka v Androidu, brzy se k ní dostaneme.
Orientace zařízení port,
land
port znamená portrait mode, tedy postavení na výšku, land znamená landscape mode, tedy na šířku.
Hustota pixelů ldpi,
mdpi,
hdpi,
xhdpi
Tyto modifikátory použijeme hlavně pro bitmapy, neboť aby byla reálná výsledná velikost zhruba stejná, budeme muset zařízením s jemnější hustotou pixelů dát obrázek větších rozměrů. Trochu si o tom povíme, až si dnes budeme povídat o jednotkách, více tehdy, až to budeme potřebovat.
API level v3,
v15
Chceme-li některé suroviny přidělit jen zařízením s úrovní API rovnou nebo vyšší nějakému číslu, můžeme použít tento modifikátor. Pozor na to, že API 3 resp. 4 (tzn. Android 1.5 a 1.6) přijmou za svou jen verzi API té jejich se přesně rovnající.

Modifikátorů je samozřejmě daleko více, všechny najdete hezky v tabulce v Providing Alternative Resources. Chcete-li použít více modifikátorů najednou, musíte je seřadit podle pořadí v tabulce odshora dolů (tzn. správně je values-cs-v15, ne values-v15-cs). V tabulce zde jsem pořadí zachoval, jen jsem toho plno vypustil.

Priorita modifikátorů

Priorita modifikátorů funguje poměrně intuitivně. Nejprve hledá Android požadovanou věc v nejspecifičtější složce a postupuje k těm nejméně specifickým. Například, máme-li složky /res/values, /res/values-en a /res/values/en-rGB a telefon má nastavený jazyk na britskou angličtinu, nejprve se podívá do třetí jmenované složky, pokud tam požadovanou surovinu nenajde, podívá se do prostřední, potom do první napsané, a pokud nebude surovina ani tam, vyhodí chybu.

Jednotky

Každé zařízení má nějakou velikost displeje. Většinou se udává jako velikost úhlopříčky v palcích, takže máme od třípalcových Androidů přes 5,3″ Samsung Galaxy Note až po desetipalcové tablety. Potom má každé zařízení rozlišení. To znamená, kolik pixelů (elementárních obrazových bodů schopných zobrazit jednu barvu) má. A pak existuje tzv. pixel density (já bych to přeložil jako hustota pixelů), většinou pod zkratkou PPI či DPI, která určuje jemnost zobrazení displeje. Má-li displej například hustotu 230 dpi, znamená to, že na každý palec fyzické velikosti displeje připadá 230 bodů, pixelů.

Pokud jste doteď vyvíjeli jen pro počítače, asi si říkáte, že je to celkem zbytečná teorie, že se monitory zase tolik neliší. To byla doteď celkem pravda, na některém monitoru by se vaše aplikace zobrazila trochu větší, na jiném menší, uživatelé jsou na to zvyklí a když tak mohou použít lupu nebo zoom. (Ale v posledním roce, vlastně od uvedení nového iPadu situace nabrala na obrátkách, výrobci začínají notebookům dávat jemné displeje, například nový Asus Zenbook Prime bude v 11palcové variantě mít Full HD rozlišení, což dává nějakých 200 dpi, což je dvakrát vyšší, než byl standard před dvěma lety (můj HP ProBook má DPI asi okolo 100), nové MacBooky Pro mají ultrajemné Retina displeje.) Na telefonech však uživatelé žádnou lupu nemají, nativní aplikace přiblížit nemohou a změna rozlišení také není možná. Pokud tedy nechcete, aby vaše aplikace dopadla tak, že se na jeden displej tlačítko vůbec nevejde, zatímco na jiném se na něj nedá ani tapnout, musíte hledat řešení.

DP

To řešení Android nabízí. Je jím jednotka dp, jež znamená density-independent pixel. Je to virtuální jednotka, která je definována matematickým vztahem 1dp = 160px/dpi. Z tohoto vztahu vyplývá, že koresponduje s fyzickou velikostí displeje. Tahle jednotka se potom na každém zařízení přepočítá zpět na pixely.

Pokud tedy chcete nastavit například okraje nějakého elementu, místo pixelů použijete dp (samozřejmě asi trochu jinou hodnotu). Jak jednoduché.

Fajn, tak rozměry view napíšu v dp. Ale co obrázky?

Dobrá otázka. U obrázků se to řeší modifikátory. Pro každou hustotu ldpi, mdpi, hdpi a xhdpi (případně tvdpi, ale to zatím u menších aplikací vzhledem k rozšířenosti Androidu v televizích nemusíme řešit) poskytněte obrázek v rozdílném rozlišení, a to v poměru 3:4:6:8. Má-li tedy obrázek pro ldpi rozlišení 18*18px, pro mdpi bude 24*24, pro hdpi 36*36 a pro xhdpi 48*48px. Pokud některou z hustot nezadáte, měla by se (snad) použít ta nejbližší. Není-li hodnota zadaná, jako výchozí se bere mdpi. Mezi hustotami pixelů jednotlivých zařízení jsou samozřejmě i jiné hodnoty, než které by vyhovovaly poměru 3:4:6:8. Android však obrázek zvětší nebo zmenší tak, aby dané hustotě odpovídal; prostě se o to dobře postará.

Šupito presto programovat

Dnes budeme rozšiřovat aplikaci z minulého článku. Pokud jste si ji nenaprogramovali, stáhněte si ji, odkaz na zdrojáky je v závěru odkázaného článku.

Současná aplikace se skládá z jedné activity, která obsahuje tlačítko a textové políčko. My ji vylepšíme tak, aby se po stisknutí tlačítka spustila nová activity, která zobrazí obsah EditText u. Do toho budou navíc smět být zapisována pouze čísla.

Nejprve vylepšíme existující layout.

Nejspíš si pamatujete, že jsem minule psal o „prasárně“, když jsme u  EditText u použili atribut android:text s řetězcovou hodnotou. To slovo jsem nepoužil jen tak, abych vypadal drsně, dokonce i Eclipse nám tvrdí, že je cosi v nepořádku. V Androidu se totiž všechny řetězce patřící do UI uchovávají jako suroviny, jednak pro jednodušší překlad, jednak pro jednodušší správu a obecně čistější kód.

Otevřeme tedy soubor /res/values/strings.xml, který je již vygenerovaný. Zobrazilo se GUI se seznamem, já však doporučuji přepnout dole záložku na čistý XML soubor. Jak vidno, jsou tam už dva tagy <string>. Ten s name="hello"  je vygenerovaný řetězec, který je použit ve vygenerovaném /res/layout/main.xml. Pokud ten layout soubor smažeme, můžeme smazat i tento řetězec. Pak je tam ještě tag <string name="app_name">. Ten obsahuje jméno aplikace, které se zobrazí v menu telefonu nebo v horní liště. app_name není žádné magické slovo, podíváte-li se do manifestu, uvidíte, že tagy <application> i <activity>  ho ve svých atributech používají. V případě tagu <application> jde o popisek zobrazený v menu, při odinstalaci atd., u <activity>  jde o titulek pro horní lištu. Pokud chcete, klidně si vytvořte dva nové rozdílné řetězce v /res/values/strings.xml  a nastavte aplikaci i activity jiné popisky.

Pokud nemá Activity nastavený titulek, použije se ten specifikovaný na tagu  <application>.

My však přidáme jiné řetězce. Nejprve popisek tlačítka. Jako potomka tagu <resources> v /res/values/strings.xml  přidejte následující řádek:

<string name="button_title">Sem klikni</string>

a v /res/layout/new_layout.xml  u  EditText u změňte hodnotu atributu android:text na @string/button_title. Jeden warning hned zmizel, ale ještě je tam druhý. Ten tvrdí, že chybí atribut inputType nebo hint. Vzhledem k tomu, že chceme jen čísla, přidáme android:inputType="number". Atribut android:inputType  určuje povolené znaky a může se mu přizpůsobit softwarová klávesnice. Má hodně možných hodnot, všechny jsou vypsány a popsány jako vždy v dokumentaci.

Ještě by se hodilo mít před vstupním políčkem popisek, který by vysvětlil, co do něj napsat. Na zobrazení textu v Androidu slouží  TextView  (mimochodem, EditText je jeho potomkem). To má mnoho různých atributů, nám dnes bude stačit jen jeden, a to android:text. Jak asi tušíte, jeho hodnota určuje, jaký text TextView zobrazí. Kromě toho ještě musíme nastavit layout atributy, tedy android:layout_width resp. android:layout_height. Do řetězcových surovin přidáme řetězec Kolik Ti je let, jemuž jako identifikátor nastavíme edittext_label, a před <EditText> vložíme následující čtyři řádky:

<TextView
    android:text="@string/edittext_label"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

Tím je úprava layoutu hotová.

PrvniKruckyActivity

Původní verze zobrazila toast a implementovala posluchače kliknutí na tlačítko. My dnes jednak přesuneme text toastu do resources a jednak hodně rozšíříme metodu buttonClicked  – ta bude muset zjistit obsah EditText u a potom spustit novou Activity, jíž zjištěné číslo předá.

Do strings.xml tedy přidáme další řetězec, s id řekněme wellcome_toast a s textem Napiš, kolik Ti je let, a pak klikni na tlačítko. Řádek s vytvořením toastu upravíme následovně:

Toast.makeText(this, R.string.wellcome_toast, Toast.LENGTH_LONG).show();

Všimněte si, že jsme tentokrát nepoužili metodu makeText se signaturou Toast makeText(Context ctx, String text, int duration), ale Toast makeText(Context ctx, int resId, int duration). Naprostá většina metod, které přijímají řetězec, který se uživateli nějakým způsobem zobrazí, má jak variantu se stringovým parametrem, tak variantu s integerovým identifikátorem.

Na chvilku bych si dovolil odbočit a zmínit se o volení správných identifikátorů. Nikde jsem nenarazil na dobře odůvodněnou radu, jaké identifikátory pro řetězce používat. Jsou víceméně dvě možnosti, každá je svým způsobem logická. Buď je jako identifikátor použit účel daného řetězce, jeho výskyt (  edittext_label, wellcome_string), anebo řetězec sám, popřípadě zkrácený popis jeho obsahu ( tap_here, please_fill_in_your_login). Já používám obě varianty, u projektů s málo řetězcovými surovinami a u řetězců, kde je jejich obsah poměrně jasný z umístění, použiju obvykle první variantu, u ostatních obvykle druhou. Rád uslyším váš názor a argumenty a rád se něčemu přiučím.

Když máme hotový uvítací řetězec, musíme ještě upravit metodu, která se zavolá po kliknutí na tlačítko. Ta bude teď takto:

public void buttonClicked(View button) {
    int age = getAge();
    if(age < 0){
        informAboutInvalidAge();
    } else{
        startAgeActivity(age);
    }
}

Metody getAge, informAboutInvalidAge i startAgeActivity  musíme implementovat.

Vím, že by objektově čistší asi bylo nesprávný (nezadaný) věk implementovat pomocí výjimky, ale v ukázkovém kódu je tohle jednodušší a přímočařejší.

getAge

Metoda getAge zjistí obsah EditText u a převede ho na číslo, které vrátí.

protected int getAge(){
    // findViewById může vrátit i null (a tehdy by program vyhodil NullPointerExteption),
    // je však běžnou praxí spolehnout se, že vše bude v pořádku.
    EditText ageInput = ((EditText)findViewById(R.id.edittext));
    String ageStr = ageInput.getText().toString();
    int age = -1;
    try{
        age = Integer.parseInt(ageStr);
    }catch(NumberFormatException e){
        // age má nastavenou výchozí hodnotu
    }
    return age;
}

Použili jsme zde jednu zajímavou metodu, a to Activity.findViewById(int id). Tato metoda jako parametr přebírá identifikátor chtěného View (identifikátor ve formě vlastnosti třídy R), které se poté pokusí najít ve svém contentView (jež jsme nastavili metodou setContentView v onCreate). Pokud ho nenajde, vrátí null, pokud uspěje vrátí View foundView. To ještě musíme přetypovat, abychom mohli použít metodu getText. findViewById si určitě zapamatujte, je to jedna z nejčastěji používaných metod.

informAboutInvalidAge

V této metodě upozorníme uživatele, že zadal nevalidní data (v praxi to znamená, že nechal políčko prázdné, neboť jsme nastavili parametr inputType), odstraníme obsah EditText u (ve skutečnosti by nemělo být potřeba, ale procvičíme si tím findViewById a metodou View.requestFocus nastavíme EditText u focus. Do strings.xml přidejte řetězec s id invalid_age a textem typu  Musíš zadat platný věk.

protected void informAboutInvalidAge(){
    Toast.makeText(this, R.string.invalid_age, Toast.LENGTH_SHORT).show();
    EditText ageInput = ((EditText)findViewById(R.id.edittext));
    ageInput.setText("");
    ageInput.requestFocus();
}

startAgeActivity

startAgeActivity spustí AgeDisplayerActivity a předá jí zjištěný věk. Nejprve si ukážeme, jak vypadá, a pak si k ní něco řekneme. AgeDisplayerActivity je Activity, jíž si vytvoříme za chvilku.

protected void startAgeActivity(int age){
    Intent intent = new Intent(this, AgeDisplayerActivity.class);
    intent.putExtra(AgeDisplayerActivity.AGE, age);
    startActivity(intent);
}

Základním komunikačním kamenem mezi jednotlivými prvky aplikací v Androidu jsou takzvané intenty. Intent je třída, která obsahuje popis nějakého záměru a případné argumenty. Intent pak předáme frameworku a ten se postará o to, aby byl splněn. Jako záměr jsme v naší aplikaci použili konkrétní třídu ( AgeDisplayerActivity.class), ale stejně tak může být záměr obecný (například Intent.ACTION_VIEW). Tehdy se pak framework podle dalších dat rozhodne, co a jakým způsobem náš intent splní. (V konkrétním případě Intent.ACTION_VIEW záleží na předaných datech k zobrazení – je-li to url adresa, spustí se prohlížeč, pokud však pro danou konkrétní url adresu naní nastaven jiný splňovatel (třeba v případě Play Store se místo prohlížeče spustí Play Store aplikace). Pokud je více možných splnitelů, nabídne se uživateli notoricky známá obrazovka s výběrem toho, kterého si uživatel přeje. Tohle všechno je ale už trochu intentová vyšší dívčí, takže to můžete zapomenout a dostaneme se k tomu později.)

Metoda putExtra přebírá dva argumenty: String name, což je unikátní identifikátor dané věci a samotnou věc, což může být jakákoli elementární hodnota anebo cokoliv, co umí Android nějakým způsobem serializovat. Druhé jmenované nás teď také nemusí zajímat, my předáváme číslo.

Activity.startActivity je jedna z metod, která přebírá Intent, a dovolil bych si tvrdit, že ta nejjednodušší. Dělá jen to, že předá androidímu frameworku daný Intent a řekne mu, že má spustit nějakou Activity, ale že ji už nezajímá, co se dál bude dít.

Tím máme dokončenou celou PrvniKruckyActivity a jdeme se vrhnout na  AgeDisplayerActivity.

AgeDisplayerActivity

Klikněte pravým tlačítkem na namespace, které je podsložkou src v projektovém stromu vlevo, vyberte New → Class. Zobrazí se průvodce vytvořením nové třídy. Do Name  napište AgeDisplayerActivity, jako Superclass  vyberte android.app.Activity a klikněte na Finish. Tím se vytvořil nový soubor, který se zároveň otevřel. Ale moc toho v něm není.

Nejprve musíme vytvořit konstantu AGE, kterou používá PrvniKruckyActivity. Hned za deklaraci třídy tedy přidejte tento řádek:

public static final String AGE = "age";

Hned po spuštění activity budeme chtít zjistit, jaký věk jí byl předán, a ten zobrazit v nějakém TextView. Potřebujeme tedy implementovat metodu  onCreate.

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

Protože nám stačí jedno jediné View, a také proto, abychom si to zkusili, tentokrát metodě setContentView nepředáme identifikátor layoutu, ale přímo TextView objekt:

    TextView tv = new TextView(this);
    setContentView(tv);

Teď musíme zjistit předaný věk. Pomocí getIntent získáme objekt Intent, který vytvořila PrvniKruckyActivity a z něj díky getIntExtra získáme věk. Předáme jí ještě pro jistotu druhý parametr, kterým je výchozí hodnota pro případ, že v  Intent u žádná vlastnost s daným klíčem není.

    Intent i = getIntent();
    int age = i.getIntExtra(AGE, -1);

Zjištěný věk a vytvořené TextView potom předáme metodě  displayAge.

    displayAge(age, tv);
}

Tím máme metodu onCreate vyřešenou. Ale ještě než se vrhneme na displayAge, přidáme si jeden řetězec do surovin.

Otevřete si tedy zase soubor /res/values/strings.xml a vytvořte řetězec s id displayed_age a textem Je Ti … moment. Tady něco nehraje. Vždyť přece musíme do řetězce vložit věk. Tak tedy vytvoříme dva řetězce, jeden s id displayed_age_1 a textem Je Ti (a nezapomeňte na mezeru na konci!) a druhý s id displayed_age_1 a textem let. To je ten nejlepší věk! (nezapomeňte na mezeru na začátku). Tyhle řetězce potom spojíme a mezi ně ještě přidáme věk.

„Ale jak spojíme? Vždyť máme jen jejich identifikátory! A vůbec, je to fujfuj. A když budeme chtít přeložit aplikaci, jak mám vysvětlit překladateli, co to znamená? A co když zapomene na ty mezery?“ Dobře, uděláme to jinak. A jak? Podíváme se do manuálu. Ten tvrdí, že máme napsat řetězec jako formátovací řetězec pro metodu String String.format(String formatter, Object... args). Smažte tedy displayed_age_1 a displayed_age_2 a místo toho přidejte jeden, s id displayed_age a textem Je Ti %1$d let. To je ten nejlepší věk!, kde %1$d je speciální formátovací sekvence a znamená to, že první argument se tam vloží jako číslo.

Dobře, ale co když je někomu 1 rok? Anebo tři roky? Nejsme přece amatéři, abychom zobrazili 1 let nebo 3 let. Dobrá, dobrá, vyřešíme i tohle. V už odkazované kapitole manuálu se o jednu sekci výše píše i o tom, jak vyřešit různé plurály. Nás bude speciálně zajímat jednička a potom malá čísla (2, 3, 4). Zbytek je stejný (včetně nuly).

Jak se píše v manuálu, místo elementu <string> musíme použít element <plurals> s několika elementy <item> jako potomky. Finální verze bude vypadat takto:

<plurals name="displayed_age">
    <item quantity="one">Je Ti, %1$d rok. To je ten nejlepší věk!</item>
    <item quantity="few">Jsou Ti, %1$d roky. To je ten nejlepší věk!</item>
    <item quantity="other">Je Ti, %1$d let. To je ten nejlepší věk!</item>
</plurals>

 

Jako identifikátor bude sloužit R.plurals.displayed_age a zformátovaný řetězec získáme metodou  Resources.getQuantityString(int resId, int quantity, Object... args).

Je tu ale jeden velký, ošklivý, nepěkná věc. Každý jazyk má totiž speciální varianty pro různá množství. Angličtina například používá jen one a other. Polština používá few pro každé číslo končící na 2, 3 nebo 4, ale ne na 12, 13 a 14. A nastavení jazyka aplikace přebírá ze systému.

Máme-li náš strings.xml uložený ve složce values bez nějakého modifikátoru, bere se, že je v ní ten jazyk, který má uživatel nastavený. Já kupříkladu používám angličtinu, takže by mi fungoval pouze plurál one, few by se úplně ignorovalo a místo toho by se použilo other. Pokud z values  uděláme values-cs, budeme muset poskytnout ještě values, protože jinak aplikace na nečeských telefonech nepůjde spustit.

Abych to shrnul, předtím, než použijete plurálovou funkcionalitu, dvakrát si rozmyslete, jestli to nepůjde jinak ( Měsíce: 7). Pro dnešek se smíříme s tím, že řekneme  Je Ti 2 let.

Tip na konec bloku

Je možné programově změnit locale, jak na to si přečtěte například v Set-up the application Language in Android Preferences. Chcete-li to dělat pro celou aplikaci, nebylo by od věci vytvořit si nějakou BaseActivity, v jejímž onCreate by se tohle provedlo. Stejně by však bylo potřeba mít i values  bez modifikátoru – kvůli názvu aplikace. A mimochodem, když už jsme u toho, vlastní BaseActivity je poměrně běžná věc, řeší se tím například sdílení menu mezi activitami.

Tím tedy máme hotové suroviny a ještě musíme implementovat metodu displayAge. Ta získá objekt Resources, od něj obdrží zformátovaný řetězec a ten zobrazí v  TextView.

protected void displayAge(int age, TextView tv){
    Resources res = getResources();
    String text = res.getString(R.string.displayed_age, age);
    tv.setText(text);
}

Hotovo!

Úspěšně jsme dokončili dnešní aplikaci. Zkusme ji tedy spustit a použít. Buť si připojte telefon, anebo spusťte emulátor, klikněte na zelenou šipku, zvolte cílové zařízení, chvíli počkejte a pak se kochejte krásou… moment! To nefunguje.

Manifest

Každý, kdo si během článku ťukal na čelo, že jsem zapomněl zapsat activity do manifestu, má jedničku s hvězdičkou. Tohle je má nejčastější chyba, není activity, již bych naprogramoval, spustil a hned na první pokus běžela – nikdy není zapsaná v manifestu. A říkal jsem si, že když ze sebe udělám hlupáka, třeba si to lépe zapamatujete :).

Otevřete tedy AndroidManifest.xml a jako potomka <application> přidejte upozornění na existující  AgeDisplayerActivity.

<activity android:name="AgeDisplayerActivity"/>

Teď už aplikace funguje.

Časté problémy

Nechce se mi zkompilovat soubor strings.xml, přestože mám všechny tagy určitě dobře uzavřené.

Nemáte někde v textu apostrof nebo uvozovky? Pokud ano, jsou dvě možnosti. Buď před každý apostrof a uvozovky v textu dáte zpětné lomítko, anebo celý text ohraničíte tím z množiny {apostrof, uvozovky}, co v textu není použito.

Aplikace se nechce spustit, hlásí ActivityNotFoundException.

Přidejte danou Activity do manifestu a zkontrolujte překlepy. Případně ji tam zkuste přidat včetně namespace.

Procvičování

Naprogramujte si aplikaci skládající se ze dvou activit. V té první se uživatele zeptáte na jméno, věk, zda je svobodný/á a poprosíte ho, aby vám napsal pár vět o sobě, a po stisku tlačítka se zobrazí druhá activity, která mu vypíše vizitku. Aplikaci lokalizujte do dvou jazyků (výchozí – values angličtina a pak čeština nebo slovenština ( values-cs či values-sk)). Odesílacímu tlačítku nastavte rozměry 200×70 dp.

Asi se vám bude hodit vědět, že View pro checkbox se jmenuje CheckBox. Pokud budete mít nějaké problémy, neváhejte se zeptat.

Dnes procvičování opravdu doporučuji, naučili jsme se toho docela hodně a příště už budu počítat s tím, že to znáte.

Závěr

Ve dnešním poměrně dlouhém článku jsme si pořádně vysvětlili suroviny, pochopili jsme, co to je a k čemu je dobrá jednotka dp, naučili jsme se vytvářet nové activity, prohodili jsme první nesmělá slova s intenty, zjišťovali a nastavovali jsme různé vlastnosti View za běhu, naučili jsme se dávat řetězce do surovin a využívat schopnosti String.format na surovinových řetězcích. Ke stažení vám jako vždy nabízím ukázkové zdrojové kódy dnešní aplikace. Máte-li jakoukoli otázku, připomínku nebo s něčím nesouhlasíte, něco byste udělali lépe, napište do komentářů.

Příště si představíme jednotlivá View, s nimiž se budete ve své praxi setkávat.

Tip na konec

diskusi pod prvním dílem se řešila poměrně zoufalá rychlost emulátorů a objevila se tam zajímavá rada, a to při vytváření AVD povolit snapshot. Doporučuji.

Komentáře

Subscribe
Upozornit na
guest
43 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Ronon Dex

Obvyklý překlad „resources“ je spíše „zdroje“. A nebál bych se podobností se slovy „source / sources / source code“ a jejich překladů „zdroják / zdrojáky / zdrojový kód“.

Mně osobně přijdou podobné obrozenecké snahy o český překlad běžných vývojářských termínů poněkud úsměvné. Ovšem nelze jim (v některých případech, včetně tohoto) upřít i slušnou dávku vynalézavosti.

Sice preferuji odborné texty v angličtině, ale chápu, že v rámci popularizace témat pro širokou veřejnost se česká verze hodí. I když dříve nebo později se člověk obecně rozšířenému jazyku autorů SDK a dokumentace naučit musí.

Ronon Dex

Sákriš, nějak jsem opomněl…

Jinak hodnotím článek pozitivně, jako celkem slušný náhled to Androidiího programování ;-)

2X4B-523P

připojuji se k pochvale a přidám do debaty svůj pohled na slovo resources

pro sebe bych mohl chápat resources jako prostředky k přístupu ke zdrojům, takže v jednom případě jsou resources prostředky a ve druhém zdroje… slovo suroviny mi osobně sedí k RAW datům…

Pavel Lang

+1 pro resources -> zdroje, co vím, tenhle překlad jsem slyšel už před snad patnácti lety v souvislosti s linkováním dat do binárek, ať už to byly string konstanty, binární data či třeba ikony – dodnes se např. ve Windows PE formátu (.exe, .dll, …) termíny resources a embedded resources používají a překlad v české literatuře byl právě jako „zdroje“.

Pokud by byl překlad matoucí, je jepší nepřekládat vůbec

phoose

Díky za článek. Jenom jedna připomínka k plurals. Konkrétně:
„Nás bude speciálně zajímat jednička a potom malá čísla (2, 3, 4). Zbytek je stejný (včetně nuly).“ a „Polština používá few pro každé číslo končící na 2, 3 nebo 4, ale ne na 12, 13 a 14.“

Abych to uvedl na pravou míru, podobně, jak je to tu uvedené u polštiny, to má i čeština. Všechna čísla končící 1 a 2 – 4 (kromě čísel končících 11 a 12 – 14) se zapisují podle stejného pravidla. Tedy např. 1 rok, 2 roky, 4 roky, 5 let, 11 let, ale dál je to 21 rok, 22 roky, 24 roky, 25 let, 103 roky. Spousta lidí to píše špatně (21 let, 103 let), protože je to pro ně jednodušší na přemýšlení (resp. na absenci přemýšlení), ale správně to má být podle zmíněného pravidla.

Nevím tedy, jak to píše Android, ještě jsem program nezkoušel. Na to budu mít čas až později.

phoose

No já osobně taky používám „lenivou“ variantu, ale byl jsem přesvědčený o tom, že to není správně. Dá se někde na stránkách ÚJČ zjistit, jak stará je ta aktualizace, od kdy je možné onu „lenivou“ variantu používat?

razor

Nebyla by u textu vhodnější jednotka „sp“?

pravdokop

Když už někdo chce zbytečně počešt´ovat, tak proč nepřeložit i Intenty jako Úmysly?

ded kenedy

podobne s layouty

RDPanek

Ahoj,
díky za perfektní článek a určitě přeji hodně energie do psaní dalších :-)

Chtěl jsem se zeptat, zda-li by šlo ke každé kapitole / ukázce appky napsat i jednotkový či integrační test – podobně jak to má v tutoriálu AngularJs.
Mohlo by se to u článků stát standardem, ať si na to programátoři zvykají, že psaní testů k aplikačkám je standard. Osobně bych to uvítal, už jen ohedně základů v JUnit? a testování android aplikací.
Mohl by s tím i Matějovi někdo pomoci?

A v poslední řadě bych jen upozornil na překlep:

řetězce, jeden s id ###displayed_a­ge_1### a textem Je Ti (a nezapomeňte na mezeru na konci!) a druhý s id ###displayed_a­ge_1### a textem let.

Ještě jednou díky za článek.

PS: br mi v komentáři nefungují :-o

Martin Hassman

Autory psaním testů zatěžovat nebudeme, stačí kolik práce s nimi mají teď. Rozhodně se ale nebráníme, pokud někdo chce testy napsat 8-)

phoose

Už se nemůžu dočkat dalšího dílu seriálu a než se dočkám, chtěl bych se zeptat na jednu věc. Když aplikaci ladím na fyzickém HW, aplikace se v podobě apk balíčku zkopíruje do zařízení (případně se napřed odinstaluje předchozí verze), nainstaluje a spustí. Všechno jede v pořádku. Když ale zdrojové kódy dané aplikace zkopíruju na jiný PC (sdílím si je mezi víc PC přes SVN), tam spustím Eclipse a chci tu stejnou aplikaci ladit na tom stejném HW zařízení, jen z jiného PC, nadává mi to, že aplikaci nejdřív musím ručně odinstalovat (Re-installation failed due to different application signatures. You must perform a full uninstall of the application.). Dá se mu nějak vysvětlit, že je mi jedno, z kterého PC stejnou aplikaci instaluju? V chybě se píše něco o „different application signatures“, je tedy možné aplikaci nějakým způsobem podepsat, aby ji bylo možné ladit z jakéhokoliv PC bez nutnosti ruční odinstalace?

phoose

Díky za vysvětlení a za odkazy. Zkusím s tím podepisováním něco pokoumat a pokud na něco přijdu, určitě se o to podělím.

Radek

Jen se chci zeptat, zda je myšleno správně u příkladu použití modifikátorů „res/values/en-GB“? Nemá tam být „res/values-en-GB“?

rogisoft

S programováním pro android začínám (jinak 25 let v různých jazycích a prostředích). Mám Eclipse Indigo, podle návodů jsem si nainstaloval všechno pro Android. Postupuji podle toho seriálu (jinak dík za něj), aplikaci „přeložím“ a je bez chyb, ale když ji spouštím přes AVD, tak se rozjede jen AVD a nic. žádné Hellou světe nebo edity a tlačítko.
Nevíte někdo mi poradit. Předem díky

Igor Vodička

rogisoft

Moje chyba! Byl jsem netrpělivý, dal jsem tomu čas a AVD se po několika minutách spustilo i s aplikací.
Přesto dík za reakci.

Jan Ptacek

V první řadě díky za super článek, už dlouho jsem se chystal začít s androidem, ale odradil mě nedostatek podrobných článků.

Myslíte, že byste mohl upravit tu část článku, ve které je uvedený kód metody getAge? Na několik minut mě zarazila, protože se mi nedařilo konečný kód hned rozběhnout. Soubory ke stažení jsem nezkoušel, je možné, že tam tahle chyba není.

místo :
String text = res.getString(R­.string.displa­yed_age, age);
by tam mělo být, pokud jsem něco nepřehlédl:
String text = res.getQuanti­tyString(R.plu­rals.displayed_a­ge, age, age);
první varianta nepůjde ani zkompilovat. Bez 3. parametru, se vypíše pouze formátovací řetězec.

Jen pro budoucí generace, které se podle tohoto článku budou chtít v androidu učit programovat, já si s tím nakonec poradil.

A ještě jednou díky za článek, doufám, že se tento seriál uchytí a budou přibývat další díly.

msx

Keď robím v eclipse na tomto ukážkovom projekte, vidím v časti Problems (dole v záložke) warningy aj z iných projektov. Ja chcem vidieť len warningy z projektu, ktorý mám práve otvorený, aby som mal prehľad. Ako to dosiahnem? Medzi toľkými warningami je ťažko si všimnúť, že mi nejaký pribudol.

xOB

Staci ostatni projekty zavrit, pravy mysitko nad projektem a Close Project.

msx

Tak to si ma pobavil. Nič v zlom. Ja tie projekty nechcem potom otvárať znova. Otvorené sú preto, lebo s nimi niečo robím. Len chcem prefiltrovať tie varovania tak, aby som videl len varovania z projektu, na ktorom mám nastavený kurzor. Skúšal som aj vytvorenie workspace, čo som myslel, že je práve na niečo také, ale nepomohlo.

xXx

Neviem ci to iba mne, ale podla navodu sa mi nepodarilo vypisat zadany vek.
Namiesto konkretneho cisla mi vypisovalo %1$d.

Vyriesl som to tym, ze som riadok:

String text = res.getQuanti­tyString(R.plu­rals.displayed_a­ge, age);
// pouzivam plurals a nie 1 identifikator

zamenil za riadok:

String text = String.format(res­.getQuantityS­tring(R.plural­s.displayed_a­ge, age), age);

Aj v tom odkaze na manual prace so String pouzivali String.format na vratenie(aj ked oni tam pracovali s metodou getString()).

dioda

Zbytečně složité. Použij:
String text = res­.getQuantityS­tring(R.plural­s.displayed_a­ge, age, age);

Kubíík

Já jenom nechápu, jak jedna activity předá hodnotu proměnný age tý druhý co to zobrazí…

trr

prikliknuti mi to pada, a netusim kde sem udelal chybu :(
domnívam se z tohoto logu http://leteckaposta.cz/633269194
ze je to tím, ze mi tam chybí nejaka reference na string? ze ji nenasel, nebo ze je spatne pojmenovana?

trr

vzkutku, nějak jsem to přehlídl :)

n00ber

Všechny články se mi líbí, ale je to trochu nepřehledné – začínám se ztrácet, člo bi udělat nějakou jednoduchou verzi – popsáno co jak dělá – řečeno co kam dát a podobně.
PS: jaká je klávesová zkratka na špičatou závorku??? :)

Pyro

(na slovenskej klavesnici)
AltGr + , =
AltGr + f = [
AltGr + g = ]
AltGr + b = {
AltGr + n = }
AltGr + q = \
AltGr + p = ‚

celkom to urychli pracu… hlavne ked sa mi nechce zvykat na anglicku klavesnicu :)

Pyro

sakra… spicate zatvorky tu nejdu pisat a skryje co je v nich.. pardon…
AltGr + , = spicata dolava (znak mensi)
AltGr + . = spicata doprava (znak vacsi)

Pyro

Zdravim,
postupoval som (skoro) presne podla navodu a ako dam aplikaciu do mojho telefonu (HTC Desire C – 4.0.3 android) a stlacim tlacitko, tak to padne. Napise ze aplikacia prestala pracovat. Napise to ale iba v pripade, ked tam zadam nejake cislo. Ak to necham prazdne, tak napise ze som zadal zle cislo. Skusal som aj zdrojaky ktore ste tu zavesili (mimochodom ked si ich dam importovat do Eclipse, tak mi asi v kazdom riadku pise chybu tak som sa to rozhodol prekopirovat do noveho projektu a uz to ide – nenadavajte mi prosim :) ) ale v tom mi to robi rovnaku chybu. Nenapada vas v com by to mohlo byt ? Ja si myslim, ze tam bude nieco nekompatibilne s novsim androidom ale to je len moj nazor. Za odpoved vopred dakujem

Pyro

ospravedlnujem sa za spam… uz mi to ide.. mal som chybicku v manifeste kt. som si nevsimol a uz mi to ide. Kazdopadne by ma zaujimalo, ako importovat vase projekty tak, aby isli

karel.cervicek.7

Aplikace funguje jen v případě, že se plní informAboutInvalidAge, avšak když vyplním věk, tak padá.
Za druhé , já nemůžu pochopit proč to řešit přes 2 activity. Nechápu proč dělat další třídu pro zobrazení výsledku. Možná je asi problém v tom, že byhc si měl projet OOP v jave.
Jinak zkoušel jsem z toho udělat aplikaci , která sčítá dvě čísla v 1 activitě, ale taky padá.
chybu to neukazuje a vše jsem překonvertoval.

public class MainActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Toast.makeText(this, „neco napis“,
Toast.LENGTH_LONG).show();
}

public void buttonClicked(View button) {
int prvni = getprvni();
int druhy = getprvni();
/*if(age < 0){
informAboutInvalidAge();
} else{
startAgeActivity(age);
}*/
int vysledek = prvni + druhy;
String text = getString(vysledek);
Toast.makeText(this, text ,Toast.LENGTH_SHORT).show();
}

protected void informAboutInvalidAge(){
Toast.makeText(this, "spatny vek" ,Toast.LENGTH_SHORT).show();
EditText ageInput = ((EditText)findViewById(R.id.prvni));
ageInput.setText("");
ageInput.requestFocus();
}

protected int getprvni(){

EditText cislo = ((EditText)findViewById(R.id.prvni));
String intcislo = cislo.getText().toString();
int Cislo = 0;
try{
Cislo = Integer.parseInt(intcislo);
}catch(NumberFormatException e){

}
return Cislo;
}
protected int getdruhy(){

EditText cislo = ((EditText)findViewById(R.id.druhy));
String intcislo = cislo.getText().toString();
int Cislo = 0;
try{
Cislo = Integer.parseInt(intcislo);
}catch(NumberFormatException e){

}
return Cislo;
}

}

Robo

Prosim vas ako pridam do druhej activity napr dva TextView aby som mohol do jedneho dat Vek a do druheho Meno? Ked tam mam len jeden tak sa to navzajom prepisuje najprv sa zobrazi meno a hned sa to prepise na vek a to meno tam uz nevidno. Dakujem.

Libor

Nemyslím že ošetřit věk výjimkou, jak píšeš, je lepší řešení. Výjimky jsou náročné a navíc tady to není nutné, protože věk 0 nedává smysl. Tudíž pokud jsi použil malý int, který má jako default 0 (nic nebylo zadano) nebo někdo záda věk 0, je to stejně špatný vstup.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.