Karty na lište akcií / Action Bar Tabs

Karty (tabs) na lište akcií

Lišta akcií v Androide 4.x významne zmenila pohľad na používateľské rozhranie. Nielenže zahŕňa tlačidlá pre význačné akcie a kebabové menu pre sekundárne činnosti v aktivite, ale môže obsahovať aktívnu ikonku... a v prípade potreby i karty tabs.

Ako hovorí dizajnový manuál, karty slúžia na prepínanie medzi rozličnými pohľadmi na údaje v aktivite alebo na prehliadanie kategórií dát.

Pekným príkladom je kategorizácia aplikácií v Google Play Store, kde vidno dve karty: pre aplikácie nainštalované na aktuálnom zariadení a pre všetky zakúpené aplikácie.

Ukážme si aplikáciu, na ktorej predvedieme použitie kariet!

Aplikácia: Slovensko: kraje, okresy

Vytvoríme si aplikáciu, ktorá bude zobrazovať zoznamy krajov a okresov Slovenska. Na jednotlivé kategórie použijeme karty.

Aplikácia Slovensko

Založme si teda aplikáciu Slovensko s jedinou aktivitou.

Štruktúra aplikácie bude nasledovná:

  • vytvoríme fragment so zoznamom krajov
  • vytvoríme druhý fragment pre zoznam okresov
  • v hlavnej aktivite vytvoríme dve karty spravujúce dva fragmenty.

Fragment pre kraje

Vytvorme v projekte triedu RegionFragment dediaci od ListFragment. Tento predlohový fragment v sebe definuje layout pre zoznam.

Dodajme do reťazcového resource súboru strings.xml pole s názvami krajov:

<string-array name="regionNames">
    <item>Bratislavský</item>
    <item>Trnavský</item>
    <item>Trenčiansky</item>
    <item>Nitriansky</item>
    <item>Žilinský</item>
    <item>Banskobystrický</item>
    <item>Prešovský</item>
    <item>Košický</item>
</string-array>

Kód aktivity potom bude pozostávať z vytvorenia adaptéra pre položky zoznamu a jeho prepojenia s fragmentom:

public class RegionListFragment extends android.support.v4.app.ListFragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        setListAdapter(ArrayAdapter.createFromResource(getActivity(), R.array.regionNames, android.R.layout.simple_list_item_1));
        return super.onCreateView(inflater, container, savedInstanceState);
    }
}

Všimnime si, ako sme využili statickú metódu createFromResource(), ktorá vie vytvoriť adaptér na základe prvkov v poli string-array v resource súbore.

V aplikácii budeme využívať knižnicu kompatibility, preto i fragmenty, i neskoršie použitá lišta akcií bude z balíčka v4, resp. v7.

Fragment pre okresy

Proces zopakujeme ešte raz: vytvoríme pole reťazcov s názvami okresov:

<string-array name="countyNames">
    <item>Bánovce nad Bebravou</item>
    <item>Banská Bystrica</item>
    <item>Žilina</item>
    ...
</string-array>

Fragment pre okresy bude takmer identický s krajovou verziou, až na referenciu na resource s položkami:

public class CountyListFragment extends android.support.v4.app.ListFragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        setListAdapter(ArrayAdapter.createFromResource(getActivity(), R.array.countyNames, android.R.layout.simple_list_item_1));
        return super.onCreateView(inflater, container, savedInstanceState);
    }
}

Implementácia aktivity

Ako pridáme novú kartu do lišty akcií? Lišta akcií, ktorú možno v aktivite získať volaním getActionBar() na aktivite, poskytuje továrenskú metódu newTab(), ktorou vytvoríme inštanciu objektu pre kartu typu ActionBar.Tab.

private void configureRegionListTab() {
    ActionBar actionBar = getSupportActionBar();

    ActionBar.Tab tab = actionBar.newTab();
    tab.setText("Kraje");
    tab.setTabListener(this);
    tab.setTag(RegionListFragment.class.getName());

    actionBar.addTab(tab);
}

Tento objekt má povinne nastaviteľný textový popisok a poslucháča, ktorý bude zodpovedný za správu obsahu aktivity pri klikaní na hlavičky kariet.

Poslucháč zmien je typu ActionBar.TabListener, a povieme si o ňom neskôr. Posledný nastaveným atribútom je tag, čo je ľubovoľný objekt asociovaný s kartou. V našom prípade si do tagu poznačíme názov triedy fragmentu, ktorý reprezentuje obsah karty. Tento tag totiž využijeme neskôr pri dynamickom zobrazovaní a skrývaní kariet.

Poslucháč zmien výberu kariet

Ak používateľ kliká na hlavičky kariet, je potrebné vymieňať obsah aktivity. A ako sa najlepšie vymieňajú obsahy? Pomocou manipulácie s fragmentami! Fragment je totiž, ako vraví definícia, znovupoužiteľný panel s komponentami a práve fragmenty budú obsahovať „telá“ jednotlivých kariet.

Na sledovanie zmien výberu kariet slúži poslucháč ActionBar.TabListener s troma metódami:

  • onTabSelected() sa zavolá pri zvolení a zobrazovaní novej karty. Tu sa tradične vytvára alebo obnovuje fragment a následne sa zobrazí.
  • onTabUnselected() sa zavolá pri skrývaní karty, ktorá bude nahradená inou vybranou kartou, čiže volá sa pri zrušení výberu karty. Tu potrebujeme odpojiť existujúci fragment od aktuálneho komponentu
  • onTabReselected() ak sa nanovo vyberie nová karta. V tejto metóde sa tradične nedeje nič.

Inicializácia aktivity

V aktivite musíme ešte zapnúť zobrazovanie kariet na lište akcií: nastavme teda režim navigácie na NAVIGATION_MODE_TABS.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    configureRegionListTab();
    configureCountyListTab();
}

Kód aktivity bude vyzerať nasledovne:

public class MainActivity extends AppCompatActivity implements ActionBar.TabListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        configureRegionListTab();
    }

    private void configureRegionListTab() {
        ActionBar.Tab tab = getSupportActionBar().newTab();
        tab.setText("Kraje");
        tab.setTabListener(this);
        tab.setTag(RegionListFragment.class.getName());

        getActionBar().addTab(tab);
    }

    @Override
    public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {

    }

    @Override
    public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {

    }

    @Override
    public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {

    }
}

Zobrazenie karty

Metóda poskytuje dva parametre: tab reprezentujúci objekt karty, ktorá sa mieni zobraziť a transakcia FragmentTransaction v rámci ktorej prebehne výmena obsahov (fragmentov) kariet.

Ako zistíme, ktorý fragment sa má zobraziť? Využijeme názov triedy fragmentu, ktorý sme si uložili do tagu pri vytváraní karty Tab.

Z karty tab teda vytiahneme tag obsahujúci názov triedy fragmentu, ktorá sa má použiť. Následne môžu nastať dva prípady:

  1. objekt fragmentu s obsahom karty ešte nebol vytvorený. V tom prípade vytvoríme inštanciu fragmentu dynamicky pomocou statickej metódy Fragment.instantiate(), ktorá vezme názov triedy fragmentu a vráti jej inštanciu. Výsledok pridáme do transakie cez metódu add(), čím pridáme fragment do aktivity. Pridávanie do transakcie zároveň využije reťazcový tag, ktorým budeme určovať v druhom prípade, či už bol fragment vytvorený alebo nie. Dôležitý je i layout, do ktorého sa pridáva fragment: využijeme koreňový layout aktivity, ktorý je reprezentovaný systémovým identifikátorom android.R.id.content.
  2. druhý, bežný prípad, nastáva, ak je už fragment s obsahom vytvorený. V takom prípade ho len vytiahneme zo správcu fragmentov a pripojíme (attach) ho na aktivitu.

Ak je popis tiahly, kód ho sprehľadní:

@Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
    String tag = (String) tab.getTag();
    String fragmentClassName = tag;

    Fragment selectedFragment = getSupportFragmentManager().findFragmentByTag(tag);
    if (selectedFragment == null) {
        selectedFragment = Fragment.instantiate(this, fragmentClassName);
        ft.add(android.R.id.content, selectedFragment, tag);
    } else {
        ft.attach(selectedFragment);
    }
}

Skrytie karty

Ak sa skrýva karta, ktorá už nie je potrebná, zavolá sa metóda onTabUnselected():

V tomto prípade získame tag z aktuálne vybranej karty, pohľadáme k nemu priradený fragment v správcovi fragmentov a ak ho nájdeme, odpojíme ho od aktivity.

@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
    String tag = (String) tab.getTag();
    Fragment selectedFragment = getSupportFragmentManager().findFragmentByTag(tag);
    if (selectedFragment != null) {
        ft.detach(selectedFragment);
    }
}

Tag v tomto prípade reprezentuje dve veci: tag súvisiaci s kartou a tag súvisiaci s identifikátorom fragmentu v transakcii. Oba tagy sú reprezentované názvom triedy fragmentu. Potom sa použije na:

  • názov triedy fragmentu, ktorá sa má vytvoriť pri výbere karty. Inými slovami, tag určuje triedu.
  • idenfikátor fragmentu uloženého pod manažment správcu fragmentov.

Transakcia v parametri FragmentTransaction sa nesmie komitnúť v kóde! Urobí to za vás správca kariet. (V opačnom prípade hrozí výnimka).

Okrem toho je zakázané uložiť transakciu na zásobník fragmentov back stack.

Znovuvybratie karty

V prípade nového výberu karty sa nestane nič: obvykle sa telo metódy necháva prázdne.

Druhá karta

Dodajme do aktivity i druhú kartu, kde kód pre vytváranie bude veľmi podobný karte krajov.

private void configureCountyListTab() {
    ActionBar actionBar = getSupportActionBar();

    ActionBar.Tab tab = actionBar.newTab();
    tab.setText("Okresy");
    tab.setTabListener(this);
    tab.setTag(CountyListFragment.class.getName());

    actionBar.addTab(tab);
}

Aplikácia beží

Aplikácia je funkčná a môžeme ju spustiť! Máme možnosť prepínať sa medzi dvoma kartami, kde sa pod každou schováva jeden fragment.

Ako ďalej?

Karty sú veľmi užitočný navigačný prvok. Ak je ich primnoho, je možno lepšie použiť výsuvný navigačný panel (navigation drawer). Druhou možnosťou je skombinovať ich s horizontálnym stránkovačom (page drawer), kde ukážu používateľovi aktuálnu polohu pri prehliadaní kolekcie prvkov.