Zoznamy sú súčasťou nejednej androiďáckej aplikácie. Otvorte si zoznam nainštalovaných aplikácií: Kontakty? Heh, veď ešte sa to aj volá “zoznam kontaktov”. Mailový klient? Vybafne na vás zoznam mailov. Twitter? Zoznam tweeteov.
Poďme si tiež urobiť jednu takú miniaplikáciu s jednou aktivitou, ktorá bude obsahovať zoznam čohosi. Čo takto zoznam úloh a. k. a. debilníček? Otrepané, ale jednoduché.
Začneme jednoducho:
- vyrobíme si klasickú aktivitu dediacu od
ActionBarActivity
- nastavíme dáta pre zoznam
- profitujeme!
Aktivita
Aktivita so zoznamom bola onehdá návrhármi API predvídavo považovaná za natoľko dôležitý element, že jej vyhradili špeciálnu triedu ListActivity
. Žiaľ, s novými verziami Androidu má táto trieda veľkú nevýhodu a to nemožnosť využívať action bar (teda lištu akcií). (Chcete vedieť, ako to bolo za tých starých dobrých čias? Pozrite sa do staršej verzie článku.
Nemusíme však plakať do vankúša. Zoznamovú aktivitu si vieme urobiť veľmi jednoducho aj bez tejto šablóny. Vytvorme novú aktivitu a nazvime ju TaskListActivity
. ADT vytvorí:
- Java kód (
TaskListActivity.java
), - layout súbor (
layout/activity_task_list.xml
). - a užitočné menu (
menu/task_list.xml
)
V Java kóde vygeneruje kostru, ktorá “nerobí nič”, hoci dostaneme zadarmo kód pre podporu kontextového menu. To však zatiaľ preskočíme a vrátime sa k nemu neskôr.
Pridanie zoznamu do aktivity
Naša zoznamová aktivita potrebuje, prirodzene, zoznam. V Androide mu zodpovedá trieda a komponent ListView
, ktorú môžeme hneď deklarovať v layoutovom súbore activity_task_list.xml
.
Úpravy v layoute
Do elementu <RelativeLayout>
dodajme nasledovnú deklaráciu:
<ListView
android:id="@+id/taskListView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
Je to jednoduché: dodáme zoznam s identifikátorom taskListView
, ktorý zaberie celú šírku a výšku okna.
Úpravy v kóde
Teraz prejdime k úpravám v kóde: v metóde onCreate()
vytiahneme náš widget podľa identifikátora do inštančnej premennej:
public class MainActivity extends ActionBarActivity {
private ListView tasksListView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_task_list);
tasksListView = (ListView) findViewById(R.id.taskListView);
}
}
Už teraz spusťme aplikáciu… aj keď neuvidíme nič zaujímavé — azda len bielu plochu. V aktivite sa nachádza prázdny zoznam, ktorý načim naplniť.
Modely a.k.a. adaptéry
V Androide platí tá istá zásada ako v každom inom rozumnom frameworku pre vývoj GUI — či už ide o Windows / Linux / Javu alebo C. Widgety sú elementy, ktoré (voľne povedané) namaľujú konkrétne dáta na obrazovku, tak, aby sa v nich používateľ vyznal. Ak máte nejaké dáta — napr. sadu fotiek — existuje viacero spôsobov, ako ich namaľovať. Pod seba, vedľa seba, v mriežke, s miniatúrou a popiskom… možností je veľa.
Dáta, podľa ktorých sa namaľuje widget, na obrazovku, sa nazývajú model. Nie nadarmo. Je to presne ako pri modeli (či presnejšie modelke), ktorá sedí na stoličke a maliar podľa nej kreslí obraz na plátno (či obrazovku).
Klasická otázka na StackOverflow.com býva:
Ako pridám dáta do zoznamu?
Odpoveď je: Nijak. Dáta nepridávate do zoznamu, ale do jeho modelu.
V prípade zoznamu v aktivite je model reprezentovaný objektom typu ListAdapter
, ktorý nesie dáta a zoznam ListView
podľa neho maľuje jednotlivé prvky na displej.
Mimochodom, ak sa pýtate, prečo sa to nevolá ListModel
…, zabŕdnete do podivných menných konvencií. Ak budem hovoriť “model zoznamu”, budem mať na mysli “list adaptér”. Všade inde (a je jedno, či ide o JavaScript, C# alebo Javu, sa hovorí o modeloch, iba v Androide sa používa pre modely označenie adaptér).
Tak či onak, filozofia je nasledovná:
- vytvoríte triedu, ktorá dedí od
ListAdapter
a alebo použijete niektorú z predpripravených polotovarov - vytvoríte inštanciu tejto triedy
- prepojíte ju s konkrétnym zoznamom
- hľadíte na svoje dietko
ArrayAdapter
— polotovar pre modely zoznamu
ArrayAdapter
je polotovar modelu zoznamu, ktorý sa hodí pre situácie, keď sú dáta v zozname nemenné, alebo sa menia len veľmi zriedka a ich pridávanie či odoberanie z modelu máte pod kontrolou. Nehodí sa však na situácie, keď chcete vyťahovať z dáta databázy, či iného dynamického zdroja — takéto situácie sa riešia inými typmi adaptérov. (Je to preto, že dáta sa v tomto adaptéri udržiavajú v poli, čím by sa viedlo k duplicite dát.)
Inštanciu ArrayAdapter
a možno viacerými spôsobmi. Najjednoduchší príklad je zoznam reťazcov s úlohami: zareprezentujeme ho klasickým javáckym zoznamom java.util.List
:
List<String> tasks = Arrays.asList("Zubár", "Elvisove narodeniny", "Svadba");
Jeden z mnohých konštruktorov môže vyzerať takto:
ArrayAdapter<String> listAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, tasks );
Význam parametrov jest:
Context
, ktorým je v tomto prípade samotný objekt aktivity, tedathis
(keďže sa nachádzame v jej vnútri).- výzor (layout) pre jednotlivé položky zoznamu. Tu vieme využiť odkaz na interné layouty zabudované v API Androidu. Layout
android.R.layout.simple_list_item_1
indikuje jednoduchú položku zoznamu, kde sa zobrazí len jednoduchý text bez všakovakých okrás. - dáta: buď v podobe zoznamu
java.util.List
alebo v podobe poľa
Posledný krok je prepojenie adaptéra/modelu so samotným zoznamom. To vykonáme pomocou metódy setListAdapter()
na zoznamovej aktivite. V metóde onCreate()
stačí po získaní inštancie ListView
zavolať:
setListAdapter(listAdapter);
Kompletný kód metódy onCreate()
:
public class TaskListActivity extends ActionBarActivity {
private ListView taskListView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_task_list);
taskListView = (ListView) findViewById(R.id.taskListView);
List<String> tasks = Arrays.asList("Zubár", "Elvisove narodeniny", "Svadba");
ArrayAdapter<String> listAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, tasks);
taskListView.setAdapter(listAdapter);
}
}
Aplikácia bude vyzerať nasledovne:
Model s civilizovanými objektami
Reťazec String
je skvelá štruktúra, lebo dá sa do nej napchať takmer všetko: akurát že vo svete OOP by sme mohli používať civilizované triedy. Zatiaľ máme len popis úlohy, ale už o chvíľu budeme chcieť zaviesť ďalšie dáta: napr. príznak dokončenia (hotovo) alebo neskôr termín/deadline:
Vyhotovme teda triedu:
package sk.upjs.ics.taskr;
public class Task {
private String name;
private boolean isDone;
private static final boolean IS_NOT_DONE = false;
public Task(String name, boolean isDone) {
this.name = name;
this.isDone = isDone;
}
public Task(String name) {
this(name, IS_NOT_DONE);
}
public boolean isDone() {
return isDone;
}
public String getName() {
return name;
}
}
Upravme aj ukážkové dáta v konštruktore našej TaskListActivity
: namiesto reťazcov String
budeme používať úlohy Task
:
List<Task> tasks = Arrays.asList(new Task("Zubár"), new Task("Elvisove narodeniny"), new Task("Svadba"));
ArrayAdapter<Task> listAdapter = new ArrayAdapter<Task>(this, android.R.layout.simple_list_item_1, tasks );
Ak teraz spustíme aplikáciu… uvidíme zoznam.
sk.upjs.ics.taskr.Task@40517880
sk.upjs.ics.taskr.Task@405178d8
sk.upjs.ics.taskr.Task@40527205
Model ListAdapter
, kde sme definovali výzor cez android.R.layout.simple_list_item_1
totiž zobrazuje v položkách reťazce, ktoré boli získané z metódy toString()
v triede Task
. Keďže v nej nemáme žiaden vlastný predpis, použije sa štandardné správanie z java.lang.Object
, ktoré vracia presne takého nezmyselné veci.
Upravme teda triedu Task
. Ak je úloha hotová, nech je výsledkom napr. Zubár [hotovo]
, inak nech je výsledkom len názov úlohy.
@Override
public String toString() {
StringBuilder sb = new StringBuilder(name);
if(isDone()) {
sb.append(" [hotovo]");
}
return sb.toString();
}
A máme to: môžeme sa tešiť z výsledného zoznamu.
Vyzera to dobre, ale škoda, že sa to neda použiť. Chýba tam definicia importov a z prikladu si človek len ťažko domysli ake importy má definovať. Škoda, lebo inak je to fajn.