Kostra projektu obsahuje dva elementy. Predovšetkým v nej nájdete implementáciu logiky hry ("engine", ak chcete) reprezentovanú ako bežnú Java triedu, čo nám uľahčí vývoj a umožní sústrediť sa na samotné používateľské rozhranie v Androide. Popri tom sa v archíve nachádza sedem ukážkových PNG obrázkov pre stav šibenice. (Obrázky sú kreslené v Paintbrushi, pokojne si ich na domácu úlohu premaľujte.)
Stiahnite si kostru projektu, aby ste ju o pár krokov využili pri nastavení vášho projektu.
Debata o návrhu používateľského rozhrania je veľmi dôležitá. Neraz práve použiteľnosť aplikácie a jej výzor dokážu buď prilákať, alebo okamžite odstrašiť budúcich používateľov.
Aké ovládacie prvky by sme mohli vyyužiť? Budeme potrebovať:
Postupne si prejdeme jednotlivé komponenty a ukážeme ich deklaráciu v súbore.
Ako sme sa naučili minule, deklarácia komponentov sa realizuje v XML súbore, tzv. layoute. V Android Studiu otvoríme súbor res\layout\activity_main.xml
prepneme sa do z návrhového režimu do režimu Text, kde môžeme priamo editovať XML.
TextView
pre hádané slovo¶Predovšetkým budeme potrebovať komponent, v ktorom bude vidno hádané slovo, teda písmená, ktoré sme v ňom uhádli. Keďže pôjde o text, ktorý používateľ nebude mať možnosť priamo upravovať, využijeme label... akurát, že ten sa v Androide volá TextView
. Čo s neuhádnutými písmenami? Budeme ich zobrazovať cez podtržníky.
<TextView
android:id="@+id/foundLettersTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:textAppearanceLarge"
android:typeface="monospace"
/>
Estetiku aplikácie zlepšíme zväčšením veľkosti textu. Namiesto uvádzania konkrétnej veľkosti v bodoch vieme využiť zabudovaný štýl pre zväčšené písmo. (Používateľ totiž môže v systéme globálne nastaviť zväčšené písmo pre všetky aplikácie a naša aplikácia by to mala zohľadniť.) Štýly v Androide sú konceptuálne podobné CSS štýlom. Môžeme si definovať vlastné štýly (teraz to robiť nebudeme), alebo môžeme využiť niektoré z ponuky Androidu.
Odkaz na zabudovaný štýl získame predponou ?android:
a názvom štýlu -- v tomto prípade textAppearanceLarge
reprezentuje "veľmi veľký text".
Podobne ako v prípade štýlov, vieme meniť ak použitú rodinu písma. Typ (typeface
) monospace
znamená neproporcionálne písmo (niečo ako strojopisný Courier).
ImageView
pre šibenicu¶Čo so šibenicou? Máme veľa možností: mohli by sme ju maľovať ručne, mohli by sme ju renderovať ako 3D šibenciu cez OpenGL, ale najjednoduchšia možnosť je využiť sedem obrázkov z kostry projektu, ktoré budú zodpovedať šiestim pokusom pre hádanie (nultý obrázok bude zobrazený na začiatku.)
Keďže Android Studio zatiaľ nepodporuje priamy import obrázkov do projektu, musíme to urobiť ručne. Skopírujte všetkých sedem obrázkov do adresára projektu (nájdete ho v hlavičke okna), do podadresára src/main/res/drawable
. Na mojom vývojovom stroji patria obrázky do adresára
c:\Users\rn\AndroidStudioProjects\Obesenec\app\src\main\res\drawable
Adresár drawable
obsahuje obrázky, ilustrácie a iné prvky, ktoré sa dajú priamo vykresliť na obrazovku. Obrázky v tomto adresári vieme priamo využiť pri deklarácii komponentu ImageView
.
ImageView
¶<ImageView
android:id="@+id/gallowsImageView"
android:src="@drawable/gallows0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
Atribút src
obsahuje odkaz na obrázok v adresári drawable
. Odkaz @drawable/gallows0
smeruje na súbor res/drawable/gallows0.png
. Prípony v tomto prípade nie sú podstatné, Android si ich dodá automaticky.
Každému by hneď napadlo, že písmená môžeme zadávať cez klávesnicu, ktorú vykreslíme používateľovi. Tým sa však vôbec nemusíme zapodievať, pretože klávesnicu dostaneme od systému automaticky. Stačí, že poskytneme používateľovi textové políčko, do ktorého bude môcť uviesť písmeno.
<EditText
android:id="@+id/letter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:textAppearanceLarge"
android:typeface="monospace"
android:hint="Guess a letter"
android:maxLength="1"
android:inputType="textCapCharacters"
/>
Textové políčko bude ukazovať veľké písmená typu monospace
. Okrem toho uvedieme hint
, teda text, ktorý sa zobrazí, ak políčko nebude obsahovať žiadne dáta. Aby sme používateľa nezmiatli, dáme mu možnosť uviesť len jedno písmeno (maximálna dĺžka jedna) a typ povolených písmen obmedzíme na veľké písmená (textCapCharacters
).
Zároveň sa musíme zamyslieť nad zadávaním písmen: používateľ zadá písmeno a ak sa náhodou preklepne, tak ho môže opraviť. Ako však povieme aplikácii, že písmeno sme už naozaj zadali?
V našom projekte sme sa rozhodli urobiť toto potvrdenie kliknutím na šibenicu. Do deklarácie ImageView
teda dodáme obsluhu pre onClick
:
<ImageView
...
android:onClick="gallowsImageViewClick"
/>
Do kódu aktivity potom dodáme metódu:
public void gallowsImageViewClick(View view)
Logika hry je reprezentovaná štandardnou Java triedou, ktorú môžeme presunúť do projektu. Na rozdiel od drawables obrázkov môžeme v tomto prípade použiť myš a drag-and-dropom presunúť triedu do adresára prislúchajúceho balíčku projektu,
Ak trieda využíva len triedy z Javy 5 a novšej, dá sa bez problémov vložiť do projektu. (Na pozadí sa skompiluje do .class
súboru, ktorý sa prevedie do formátu .dex
používaného virtuálnym strojom Dalvik.)
Trieda poskytuje nasledovné metódy:
CharSequence getGuessedCharacters()
vráti reťazec s uhádnutými znakmi. Neuhádnuté znaky sú reprezentované podtržníkmi, čiže slovo uprostred hádania je _nt__t_r
,boolean guess(char character)
, ktorá vráti true
, ak sa zadané písmeno nachádza v hádanom slove,int getAttempsLeft()
, ktorá vráti počet zostávajúcich pokusov na hádanie. Začíname so šiestimi pokusmi.boolean isWon()
, ktorá zistí, čí sme uhádli všetky písmená v slove,String getChallengeWord()
, ktorá vráti celé hádané slovo.Nová inštancia hry znamená tiež vygenerovanie náhodného slova na hádanie. Ak chceme hádať iné slovo, musíme vytvoriť novú inštanciu hry.
Stav hádania vieme získať z metódy getGuessedCharacters()
. Ak sa háda povedzme štvorpísmenné slovo, na začiatku uvidíme štyri podtržníky vedľa seba, ktoré sa pri vykresľovaní zlejú do jednej vodorovnej čiary čo môže používateľa miasť. Preto si vytvoríme pomocnú formátovaciu metódu, ktorá vloží medzi každé dva znaky jednu medzeru.
private CharSequence formatLetters(CharSequence string) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < string.length() - 1; i++) {
result.append(string.charAt(i)).append(" ");
}
result.append(string.charAt(string.length() - 1));
return result;
}
Programovanie aplikácie je v tejto chvíli práca, ktorá prepojí stavy hry so vzhľadom používateľského rozhrania. Väčšina sa bude odohrávať v metóde gallowsImageViewClick()
.
Na čo si dáme pozor:
Šibenica je reprezentovaná siedmimi obrázkami v adresári res/drawable
. Čím menej pokusov ostáva, tým vyšší obrázok chceme zobraziť. Prakticky máme situáciu, keď potrebujeme pre itý zostávajúci pokus zobraziť (7-i)-ty obrázok.
K obrázkom, ktoré sú drawable, vieme pristúpiť cez klasickú pomocnú triedu R.drawable.[názovObrázkuBezPrípony]
.
Keďže nemáme rozumný spôsob, ako dynamicky vytvoriť názov používaného obrázku, vieme to obísť cez pole obrázkom, ktoré deklarujeme ako inštačnú premennú.
private int[] gallowsLayouts = {
R.drawable.gallows0,
R.drawable.gallows1,
R.drawable.gallows2,
R.drawable.gallows3,
R.drawable.gallows4,
R.drawable.gallows5,
R.drawable.gallows6
};
Jednotlivé obrázky sú reprezentované odkazmi na súbory z adresára, ktoré sú v Androide reprezentované číslami int
. (Ak poznáme C, môžeme si to predstaviť ako pointre do príslušného adresára.)
Toasty slúžia na stručné informačné oznámenia, ktoré používateľ zoberie na vedomie, a ktoré príliš neberú jeho pozornosť a fokus. Toasty netreba odklikávať a predstavujú tzv. flash notifications.
Toast.makeText(this, "You must enter a letter.", Toast.LENGTH_SHORT)
.show();
Na vytvorenie toastu potrebujeme metódu makeText()
, ktorá potrebuje nasledovné parametre:
Context
: v tomto prípade predstavuje aktivitu, z ktorej vytvárame toast. Referenciu na súčasnú aktivitu získame z premennej this
.Toast
sú k dispozícii dve konštanty pre krátke (Toast.LENGTH_SHORT
) a dlhé (Toast.LENGTH_LONG
) trvanie zobrazeniaPre indikáciu výhry a prehry budeme používať farebné filtre obrázka. Ak je hra vyhratá, šibenica ozelenie, a ak je prehratá, očervenie.
Červený filter vieme vytvoriť a nastaviť na obrázku nasledovne:
ColorFilter filter = new LightingColorFilter(Color.RED, Color.BLACK);
gallowsImageView.setColorFilter(filter);
Ak chceme zrušiť filter, použijeme null
ový filter.
Každá aplikácia má možnosť reagovať na zmeny životného cyklu. Bežným príkladom je metóda onCreate()
, ktorá sa zavolá vo chvíli, keď je vytváraná nová, čerstvá, inštancia aktivity. (Konštruktory aktivít sa v Androide nepoužívajú.) V tejto metóde prebehne napríklad vytvorenie všetkých ovládacích prvkov.
Naša aplikácia však nereaguje na zmenu stavov korektne. Ak rozohráme hru a otočíme displej, aktivita sa zničí a vytvorí nanovo. (V skutočnosti otočenie displeja znamená zmenu konfigurácie systému, na čo aktivita nemôže reagovať inak než zničením a znovuspustením.) Prejaví sa to vygenerovaním nového slova, úplným resetom hry vrátane šibenice. To môže byť samozrejme buď pozitívne (ak prehrávame), ale vo väčšine prípadov neželané správanie.
Našťastie máme možnosť ukladať stav aktivity tak, aby hra dokázala prežiť aj úplné zničenie a obnovenie používateľského rozhrania.
Čo je v tomto prípade stav aktivity? Tvorí ho obsah textového políčka s písmenom, aktuálny obrázok šibenice a obsah políčka s uhádnutými písmenami. Stav aktivity, teda vzhľad používateľského rozhrania, v skutočnosti odráža stav objektu HangmanGame
.
Ak aktivita odchádza na pozadie, máme možnosť uložiť stav aktivity, a to pomocou metódy onSaveInstanceState()
a jej parametra typu Bundle
.
Bundle
je mapa, do ktorej môžeme uložiť objekty, ktoré majú prežiť reštart aktivity. Predstaviť si ju môžeme ako zaváraninové poháre, do ktorých si odložíme požadované veci na "zimu", teda na moment, keď bude aktivita zničená.
Objekty uložené v bundle si môžeme potom vyzdvihnúť v metóde onCreate()
, ktorá má identický parameter. Ak je aktivita zabitá a znovuvytvorená, v Bundle
príde mapa s predošlými uloženými hodnotami. V opačnom prípade je parameter bundle
rovný null
.
onSaveInstanceState()
¶Pri implementácii onSaveInstanceState()
nezabudnime zavolať rodičovskú implementáciu cez super.onSaveInstanceState()
!
Ak celú triedu HangmanGame
vyhlásime za Serializable
, vieme ju ľahko pchať do Bundle
cez metódu putSerializable()
a podobne vytiahnuť z bundle cez getSerializable()
.
Stav komponentu, ktorý má priradený identifikátor (teda nastavené android:id
), je automaticky ukladaný a obnovovaný bez toho, aby sme museli čokoľvek programovať
Ukladanie stavu do bundle sa týka prechodného stavu, teda prípadov, keď potrebujeme uložiť a obnoviť stav aktivity medzi chvíľami, keď je aktívna a chvíľami, keď je na pozadí.
Ak chceme ukladať perzistentný stav, ktorý má pretrvať aj reštart telefónu, musíme využiť SQL databázu, súbory a pod. o čom si povieme v budúcnosti
Zdrojové kódy aplikácie sa nachádzajú na Githube.