Android: Zobrazovanie histórie volaní: prispôsobenie výzoru položiek cez ViewBinder

Úvod

V predošlom dieli sme si ukázali ako vytvoriť aktivitu, kde sa v zozname zobrazujú telefónne čísla z histórie volaní. Úprimne povedané, ten zoznam nebol “nič moc”, ukazoval totiž len samotné čísla bez akýchkoľvek ďalších informácií.

Skúsme si ho o niečo vylepšiť: použime zoznam, kde každá položka pozostáva z dvoch podpoložiek: väčším písmom je zobrazené telefónne číslo a menší font zobrazuje dátum uskutočnenia hovoru.

SimpleCursorAdapter štandardne zoberie hodnotu zo stĺpca v tabuľke z content providera, a zobrazí ju pomocou príslušného widgetu. Telefónne číslo (stĺpec s menom z konštantny CallLog.Calls.NUMBER) sa zobrazí bez problémov. Čo spravíme s dátumom?

Dátum uskutočnenia hovoru sa nachádza v stĺpci s menom v konštantne CallLog.Calls.DATE a jeho hodnota je long s počtom milisekúnd od počiatku epochy, čo je v súlade s tradičnou UNIXáckou reprezentáciou. Toto číslo však zjavne nemôžeme zobraziť používateľovi. Ako ho prevedieme na civilizovaný reťazec?

V adaptéri potrebujeme upraviť správanie medzi momentom, keď sa vytiahne hodnota zo stĺpca tabuľky a chvíľou, keď sa hodnota namapuje na widget. Dosiahneme to vlastnou implementáciou ViewBindera:

Mapovanie medzi dátami a widgetami: SimpleCursorAdapter.ViewBinder

Interfejs SimpleCursorAdapter.ViewBinder slúži presne na našu situáciu: má jednu metódu:

public boolean setViewValue(View view, Cursor cursor, int columnIndex)

V parametri dostaneme view, teda widget, na ktorý sa má namapovať príslušná hodnota zo stĺpca tabuľky. Kurzor bude nastavený na príslušný riadok tabuľky a columnIndex obsahuje index stĺpca v tabuľke, z ktorého máme namapovať dáta na widget. Táto metóda sa zavolá pre každý riadok toľkokrát, koľko stĺpcov je vo výsledku z kurzora.

Metóda má vrátiť true, ak úspešne vykonala mapovanie medzi dátami z tabuľkami. Ak vráti false, indikuje tým kurzorovému adaptéru, že má použiť vlastné štandardné správanie, ktoré sme videli v predošlom dieli.

Celý kód pre náš príklad vyzerá:

public class CallLogEntryViewBinder implements ViewBinder {
    @Override
    public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
        if(view instanceof TextView) {
            TextView textView = (TextView) view;
            String value = "";
            if(CallLog.Calls.DATE.equals(cursor.getColumnName(columnIndex))) {
                value = new Date(cursor.getLong(columnIndex)).toLocaleString();
            } else {
                value = cursor.getString(columnIndex);
            }
            textView.setText(value);
            return true;
        } else {
            // binding neprebehol, nech si ho SimpleCursorAdapter spravi sam
            return false;
        }
    }

}

Zistenie typu widgetu

Oba stĺpce našej tabuľky budeme mapovať na textové položky. Ak nám kurzorový adaptér ponúkne akýkoľvek iný komponent než typu TextView (teda klasický statický “label”), budeme to považovať za neštandardné správanie a vrátime false (“neviem, čo mám robiť s tvojim view, vysporiadaj sa s tým sám.”). V pozitívnom prípade prevedieme view na správny typ TextView, aby sme s ním vedeli pracovať.

Ošetrenie dátumu

V ďalšom prípade potrebujeme ošetriť prípad pre stĺpec dátumu, kde musíme vykonať prevody medzi milisekundami od počiatku epochy a civilizovaným reťazcom z dátumom.

Ako zistíme aktuálny stĺpec? Máme k dispozícii columnIndex, ale ten môže byť kadejaké podivné číslo. Volaním metódy zistíme jeho názov:

cursor.getColumnName(columnIndex) 

Nesnažte sa spoliehať na to, že telefónne číslo bude v nultom stĺpci a dátum v prvom: to je pravda len v prípade, že ste správne naprojektovali stĺpce v metóda query() (ak neviete, čo je projekcia, nie je to dôležité).

Teraz sa už len stačí spýtať, či sa názov zhoduje s názvom dátumového stĺpca.

Prevod medzi číslom a dátumom urobíme pomerne hlúpo, ale bude to musieť stačiť. V jednom riadku získame longovú hodnotu z dátumového stĺpca (vo vnútri ifu sme si istí, že obsluhujeme dátumový stĺpec, teda, že columnIndex je správny). Konštruktor dátumu Date vie pracovať s milisekundami a získať inštanciu, na ktorej zavoláme konverziu na reťazec v súlade s miestnymi jazykovými nastaveniami, čím získame reťazec zobrazený vo widgete.

new Date(cursor.getLong(columnIndex)).toLocaleString();

V prípade iných stĺpcov je prevod jednoduchý: zoberieme stringovú hodnotu z kurzora a je to.

Len mimochodom: ak ste pracovali so Swingom a zoznamami, filozofia je podobná ListCellRendereru.

Úprava aktivity

ViewBinder je hotový, poďme upraviť aktivitu. V prvom rade potrebujeme upraviť polia from a to, keďže už mienime zobrazovať dve podpoložky v každej položke:

String[] from = { CallLog.Calls.NUMBER, CallLog.Calls.DATE };
int[] to = { android.R.id.text1, android.R.id.text2 };

Uviedli sme v nich názvy stĺpcov, resp. príslušné identifikátory textových políčok, na ktoré sa namapujú. Položky android.R.id.text1 a android.R.id.text2 sa zdajú byť vycucané z prstu: v skutočnosti sa nachádzajú v layoute android.R.layout.simple_list_item_2 (dvojpodpoložkový layout pre zoznamy), ktorý použijeme v kurzorovom adaptéri:

SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2, cursor, from, to);

Nesmieme zabudnúť nastaviť náš view binder!

adapter.setViewBinder(new CallLogEntryViewBinder());

To je, čudujsasvete, posledný krok.

Čo sa udeje vo vnútri?

Kurzorový adaptér zavolá metódu setViewValue() v našom view binderi pre každý riadok a pre každý view. U nás máme dva viewy, a to textové typu TextView: android.R.id.text1, android.R.id.text2).

Kompletné kódy

package sk.upjs.ics.novotnyr.android.calllog;

import android.app.ListActivity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.CallLog;
import android.provider.MediaStore;
import android.widget.ListAdapter;
import android.widget.SimpleCursorAdapter;

public class MainActivity extends ListActivity {
    private static final String[] NO_PROJECTION = null;

    private static final String NO_SELECTION = null;

    private static final String[] NO_SELECTION_ARGS = null;

    private static final String NO_SORT_ORDER = null;

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

        String[] from = { CallLog.Calls.NUMBER, CallLog.Calls.DATE };
        int[] to = { android.R.id.text1, android.R.id.text2 };

        Uri contentProviderUri = CallLog.Calls.CONTENT_URI;

        Cursor cursor = getContentResolver().query(contentProviderUri, NO_PROJECTION, NO_SELECTION, NO_SELECTION_ARGS, NO_SORT_ORDER);
        startManagingCursor(cursor);

        SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2, cursor, from, to);
        adapter.setViewBinder(new CallLogEntryViewBinder());
        setListAdapter(adapter);
    }

}

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *