180 minút s Androidom: 6. stretnutie [Kalendárik s farebnými dňami]

Ukážková aplikácia

Vytvorme aplikáciu, ktorá zobrazí kalendárik, v ktorom si môžeme začiarkavať dni jednou z vybraných farieb.

Predpripravené materiály

Výsledná aplikácia

Viď projekt DayGrid na GitHube. Dáta sú predbežne uložené v pamäti.

daygrid1

Elementy, ktoré sa neriešili

  • posúvanie kalendára o mesiac dozadu či dopredu (zobrazujeme fixný dátum)
  • action bar
  • ukladanie stavu inštancie aktivity

Poznámka pre budúce generácie

Z hľadiska rozloženia času je lepšie najprv ukázať farbenie jednou pevnou farbou (teda zatiaľ ignorovať dialógy a ich buildery) a ušetrený čas venovať dokončeniu content providera. Zároveň je vhodné neskúšať rovno databázový content provider, ale naučiť sa najprv pamäťový a v budúcom stretnutí ho rozšíriť.

Koncepty, ktoré zvládneme

  • vlastný layout komponentov na príklade tabuľky
  • použitie užitočnej triedy android.util.MonthDisplayHelper
  • práca s poľami reťazcov v resources
  • dialógy a ich buildery
  • tvorba vlastných content providerov

Vlastné layouty

  • dosiaľ sme layout vždy načítavali z XML súboru
    • uložený v res/layout
    • nafúknutý cez setContentView()
  • layouty však môžeme vyrábať aj ručne
    • štýl ako v Swingu

Kalendár a tabuľkový layout

  • kalendár má fixný počet stĺpcov (dní): sedem
  • rovnako fixný počet riadkov (týždňov): šesť
  • určite sa nám nechce definovať XML s 7 x 6 elementami

Tabuľkový layout

  • trieda TableLayout
  • filozofia a la HTML:
    • pozostáva z riadkov (TableRow)
    • a buniek (ľubovoľný widget)
  • stĺpce natiahneme cez setStretchAllColumns(true)
  • nezabudnime na korektnú hierarchiu:
    • pridávať bunky do riadkov cez row.addView()
    • a pridávať riadky do tabuľky cez table.addView()
    • a tabuľku do aktivity cez setContentView()
  • v našej aplikácii dva for cykly.

Obsluha udalostí

  • prakticky vytvoríme dynamicky 7 x 6 buniek typu TextView
  • vytvoríme klasicky obsluhu ich kliku cez setOnClickListener()
  • najlepšie je delegovať vnútro metódy do metódy v aktivite
    • zvýši sa prehľadnosť kódu

Míľník: vidíme naplnenú tabuľku

  • rozbehnite aplikáciu tak, aby v tabuľke ukazovala riadkové a stĺpcové koordináty
  • sledujte správanie pri rotovaní displeja
    • stĺpce sa samé naťahujú

Pomocná trieda MonthDisplayHelper

  • zabudovaná trieda na kreslenie kalendárika
  • inicializujeme ju:
    • rokom
    • mesiacom (pozor, indexované od nuly!) ako v java.util.Calendar
    • prvým dňom v mesiaci: na Slovensku začíname Calendar.MONDAYom.
  • vieme jej dať index riadku a stĺpca a vráti kalendárny deň v mesiaci
  • využijeme ju pri napĺňaní tabuľky

Estetika tabuľky

  • nastaviť farbu pozadia cez textView.setBackgroundColor()
  • nastavme centrovanie cez setGravity(Gravity.CENTER)
  • osivieť bunky môžeme cez setEnabled(false)
    • zabránime tým kliku, ale to u nás nevadí
    • využijeme metódu isWithinCurrentMonth(), ktorá vráti true, ak je stĺpec a riadok v aktuálnom mesiaci.

Míľník

Rozbehnime aplikáciu, ktorá zobrazuje dátumy pre pevne zadefinovaný mesiac a rok.

Doménové objekty a klik na políčko

Ako zistiť, ktorý dátum sa skrýva pod konkrétnym políčkom tabuľky? Musíme vedieť, na ktorý dátum sme klikli.

Doménový objekt

Vieme vytvoriť separátny doménový objekt reprezentujúci dátum:

  • API pre java.util.Date() je zastaralé
  • API pre java.util.Calendar() je WTF (mesiace sú indexované od nuly)
  • Joda Time API ešte nemáme
  • potrebujeme prevod na linuxový timestamp (kvôli databázam)
  • a prevod na reťazec pre podmienky v SQLite

Klik na políčko

  • asociujme konkrétny doménový objekt s políčkom
  • každý objekt typu View má metódu setTag(), kde vieme priradiť komponentu ľubovoľné dáta
  • v obsluhe kliku vieme z komponentu, na ktorý sa kliklo, vytiahnuť doménový objekt dátumu a pracovať s ním cez getTag()

    DayMonthYear date = (DayMonthYear) dayTextView.getTag();
    

Výber farby

V prvej verzii podporujeme len zabudované farby:

  1. Deklarujeme názvy farieb v resources.
  2. Vytvoríme enum pre farby.
  3. Vytvoríme dialóg pre ich výber

Názvy farieb v resources

V strings.xml deklarujeme farby:

<string-array name="colors_array">
    <item>Červená</item>
    <item>Modrá</item>
    <item>Zelená</item>
    <item>Fialová</item>
    <item>Oranžová</item>
    <item>Biela</item>
    <item>Čierna</item>
    <item>Bez farby</item>
</string-array>

Tieto farby definujú len názvy pre ľudí, teda použíateľov.

Enum pre farby

  • V kóde využijeme enum: farieb na výber je totiž len konečný a nemenný počet. V Androide síce existuje trieda android.util.Color, ale to je len biedny obal s konštantami a niekoľkými pomocnými metódami. V skutočnosti je androidná farba reprezentovaná ako int a táto pomocná trieda rieši prevody medzi reprezentáciami.

  • Poradie farieb v enume musí byť rovnaké ako poradie v resourcoch!

  • Zdrojový súbor je enum Daycolor.

Výber farby

  • výber farby urobme v modálnom dialógu
  • neoplatí sa vytvárať aktivitu, tá je príliš ťažkotonážna. Dialógy sa hodia presne pre prípady, ak chceme od používateľa vyžiadať informácie, bez ktorých sa nepohneme pri danej postupnosti krokov
  • na vybudovanie dialógu slúži AlertDialog.Builder:

        new AlertDialog.Builder(this)
            .setTitle("Vyberte farbu")
            .setItems(R.array.colors_array,
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog,
                                int which) {
                            colorize(date, DayColor.values()[which]);
                            prepareLayout();
                        }
    
                    })
            .show();
    
  • definujeme titulok title

  • zoznam položiek do výberu, ktorý vytiahneme z resources
  • obsluhu pre klik v podobe OnClickListenera.
    • pozor, toto je iný DialogInterface.OnClickListener, čo je iný interfejs ako ten, ktorý sa používa v obsluhe kliku na políčko tabuľky! (musíme ho uviesť názvom triedy).
    • dostaneme k dispozícii which, čo je index položky, na ktorú sa kliklo
  • metódou show() vybudujeme a zobrazíme dialóg.

daygrid2

Providery

  • prostriedok pre prístup k tabuľkovým dátam
  • automaticky beží v separátnom vlákne!
  • videli sme v akcii pri minulom stretnutí

Vlastný provider

  1. vlastná trieda dediaca od android.content.ContentProvider˛
  2. deklarácia v manifeste

Deklarácia v manifeste

Provider potrebuje:

  • názov triedy, ktorá ho implementuje v atribúte name
  • symbolické meno pre providera: autorita (authority)
    • typicky v tvare názovBalíčkaAplikácie.provider
    • ostatné aplikácie využívajú autoritu na vyhľadanie konkrétneho providera
  • či je provider dostupný: enabled
  • či je exportovaný pre použitie v cudzích aplikáciách: exported

    <provider
        android:name="sk.upjs.ics.novotnyr.daygrid.DayColorContentProvider"
        android:authorities="sk.upjs.ics.novotnyr.daygrid.provider"
        android:enabled="true"
        android:exported="false" >
    </provider>
    

Vlastná trieda

  • potrebujem definovať content URI, adresu, ktorá jednoznačne identifikuje providera a konkrétnu tabuľku
  • v tvare content://autorita/tabuľka
  • zvoliť si ju môžeme ľubovoľne, v našom prípade:

    content://sk.upjs.ics.novotnyr.daygrid.provider/daycolor
    

    Táto content URI pristupuje k tabuľke daycolor.

  • alternatívne vybudujme pomocou triedy Uri.Builder:

    private static final String AUTHORITY = "sk.upjs.ics.novotnyr.daygrid.provider";
    
    private static final String DAYCOLOR_TABLE = "daycolor";
    
    public static final Uri YEAR_MONTH_DAY_COLOR 
        = new Uri.Builder()
            .scheme(ContentResolver.SCHEME_CONTENT)
            .authority(AUTHORITY)
            .appendPath(Database.DayColor.TABLE_NAME)
            .build();
    

Názvy tabuliek

  • Konvencia je vybudovať typovo bezpečný popis schémy: názvy tabuliek a ich stĺpcov
  • Predídeme tým lepeniu reťazcov s názvami tabuliek a stĺpcov
  • Uľahčuje prípadný refactoring, ak sa zmení schéma databázy.

Príklad pre databázu daygrid s jednou tabuľkou daycolor a štyroma stĺpcami.

import android.provider.BaseColumns;

public class Database {
    public static final String NAME = "daygrid";

    public static final int VERSION = 1;

    public interface DayColor extends BaseColumns {
        public static final String TABLE_NAME = "daycolor";

        public static final String TIMESTAMP = "timestamp";
        public static final String COLOR = "color";
    }   
}

Stĺpec _id sa automaticky zdedí z interfejsu BaseColumns.

Prekrytie metód

  • onCreate(): pri vytvorení content providera. Beží vo vlákne UI, musí preto zbehnúť rýchlo. Ak využívame databázu, nesmieme tu získavať objekt databázy cez get***Database(), keďže je to pomalá operácoa
  • insert(), query(), update() a delete(): operácie pre vyberanie dát, resp. ich modifikáciu. Bežia v samostatnom vlákne.
  • getType(): identifikácia množstva dát, ktoré sa vrátia z dopytu pri query(): buď jeden objekt alebo sada.

Operácie nad dátami a URI

  • každá z metód pracujúcich nad dátami má parameter URI
  • je to content URI, kde
    • musíme zistiť, či ju náš content provide podporuje
    • a voliteľne z nej vytiahneme dáta

Návrh URI pre kalendár a rozhodovacia logika pre podporu

Návrh URI

Dohoda:

content://sk.upjs.ics.novotnyr.daygrid.provider/daycolor/2014/03/15

URI pre prácu s farbou 15. marca 2014.

Príprava UriMatchera

Rozhodovaciu logiku i parsovanie rieši android.content.UriMatcher. Použijeme ho ako inštančnú premennú:

private UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

V metóde onCreate() definujeme pravidlo pre zhodu:

uriMatcher.addURI(AUTHORITY, Database.DayColor.TABLE_NAME + "/*/*/*", YEAR_MONTH_DAY_COLOR_CODE);
  • pre autoritu v konštante definujeme sufix a tri hviezdičkové zástupné znaky zodpovedajúce trom segmentom v ceste, ktoré sú reťazcového typu
  • zároveň tomuto pravidlu priradíme číselný kód definovaný v našej konštante:

    private static final int YEAR_MONTH_DAY_COLOR_CODE = 0;

Použitie matchera:

Pre URI z parametra metódy contentprovidera zavoláme metódu match() a vráti sa nám číselný kód pravidla, oproti ktorému oswitchujeme:

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    switch(uriMatcher.match(uri)) {
    case YEAR_MONTH_DAY_COLOR_CODE:
        return queryDayColorForYearAndMonth(uri);
    default:
        return null;
    }
}

Hodnoty jednotlivých segmentov získame cez uri.getPathSegments().get(i), kde i je index segmentu (od nuly).

Zistenie množstva dát

Kurzory s jediným riadkom

Ak metóda query() vracia jediný riadok, musí vrátiť špeciálny MIME typ. Prefix musí byť vnd.android.cursor.item, sufix definujeme my, autori content providera:

vnd.android.cursor.item/vnd.sk.upjs.ics.novotnyr.daygrid.provider.daycolor

V kóde:

private static final String MIME_TYPE_DAYCOLOR_SINGLE_ROW = CURSOR_ITEM_BASE_TYPE + "/vnd." + AUTHORITY + "." + Database.DayColor.TABLE_NAME;

Kurzory s nula a viacerými riadkami

Ak metóda query() vracia nula či viac riadkov, použijeme prefix vnd.android.cursor.dir

vnd.android.cursor.dir/vnd.sk.upjs.ics.novotnyr.daygrid.provider.daycolor

Výber typu

Typ samozrejme môžeme opäť naswitchovať:

@Override
public String getType(Uri uri) {
    switch(uriMatcher.match(uri)) {
    case YEAR_MONTH_DAY_COLOR_CODE:
        return MIME_TYPE_DAYCOLOR_SINGLE_ROW;
    default:
        return null;
    }       
}

Aktualizačné operácie

Je slušné notifikovať poslucháčov o zmene dát cez:

getContext().getContentResolver().notifyChange(uri, NO_CONTENT_OBSERVER);

kde NO_CONTENT_OBSERVER je konštanta, za ktorou sa skrýva null.

Použitie providera v kóde aktivity

  • Využijeme, ako minule content resolver, do ktorého pošleme príslušnú URI.
  • Našu URI vybudujeme pomocnou metódou:

    content://sk.upjs.ics.novotnyr.daygrid.provider/daycolor/2014/03/15
    
  • Budujme cez:

    Uri uri = Uri.withAppendedPath(DayColorContentProvider.YEAR_MONTH_DAY_COLOR, String.valueOf(date.getYear()));
    uri = Uri.withAppendedPath(uri, String.valueOf(date.getMonth()));
    uri = Uri.withAppendedPath(uri, String.valueOf(date.getDay())); 
    

Zdroje

Pridaj komentár

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