Prvá aplikácia: Kocka

Cieľové elementy

Vytvoríme si prvú aplikáciu: kocku! Vždy, keď klikneme na tlačidlo, sa vygeneruje náhodné číslo, ktoré si zobrazíme.

Koncepty, ktoré zvládneme

  • sprievodca vytvorením nového projektu
  • štruktúra projektu v Android Studiu
  • vytvorenie aktivity
  • XML layout aktivity a definícia widgetov
  • práca s widgetmi v kóde Javy

Požiadavky

Predpokladáme, že máte nainštalované Android Studio a všetky súčasti. Ak nie, nasledujte kapitolu Ako začať?

Výsledná aplikácia

Ako vytvoriť nový projekt?

V tejto fáze by sme mali mať nainštalovanú Javu i Android Studio. Poďme programovať! Každá androidová aplikácia potrebuje mať v Android Studiu nový projekt. Ako ho vytvoriť?

Jedna z možností je využiť možnosť Start a new Android Studio project z úvodnej obrazovky Welcome to Android Studio.

Nastavenie vlastností projektu

Pri konfigurovaní projektu potrebujeme nastaviť:

  • Application Name: komerčne lákavý názov aplikácie, ktorý uvidí koncový používateľ v zozname aplikácií
  • Company Domain: internetová doména spoločnosti. Na jej základe sa odvodí názov balíčka
  • Package Name: názov balíčka pre triedy v Jave. Odvodí sa automaticky z názvu internetovej domény, možno ho však prispôsobiť. V názve sa nesmie nachádzať diakritika. Pozor! Názov balíčka jednoznačne identifikuje vašu aplikáciu v obchode Google Play! Ak neskôr zmeníte názov balíčka, vytvoríte tým novú aplikáciu, a vaši používatelia prídu o možnosti aktualizácií. (Ak je vaša aplikácia platená, po zmene balíčka, si ju používatelia budú musieť kúpiť nanovo.)
  • Project Location: cesta k adresáru s projektom. Na Windowse nepoužívajte v názve adresára diakritiku.

Nastavenie cieľovej platformy

V ďalšom kroku nastavíme minimálnu verziu platformy, ktorú bude naša aplikácia podporovať. Čím nižšia je podporovaná verzia, tým viac starších zariadení bude môcť spustiť vašu aplikáciu, a na druhej strane, vyššie verzie poskytujú pestrejšie možnosti, prípadne moderný dizajn a widgety. Voľba platformy je často marketingovým rozhodnutím, ale ak sa rozhodnete pre Android 4.0.3, ktorý vo februári 2015 podporuje vyše 90% zariadení, neurobíte chybu.

Ak nemáte k dispozícii požadovanú platformu (štandardne je nainštalovaný len Android 5.0), môžete ju doinštalovať po dokončení sprievodcu a zmeniť v aplikácii dodatočne.)

Pridanie novej aktivity

Aktivita v Androide predstavuje analógiu jedného okna, či „obrazovky“ z aplikácií na stolných počítačoch. Bežné aplikácie potrebujú aspoň jednu aktivitu, bez nich by totiž nemali prakticky žiaden spôsob komunikácie s používateľom.

V tejto úvodnej fáze vytvoríme prázdnu aktivitu.

Nastavenie vlastností novej aktivity

Pre každú aktivitu potrebujeme nastaviť jej vlastnosti. V tejto fáze ponecháme návrhy od Android Studia.

  • Activity Name: názov triedy aktivity v Jave. Nesmie obsahovať diakritiku.
  • Layout Name: názov layoutu, teda XML súboru, ktorá definuje vzhľad a vizuálne rozloženie ovládacích prvkov v "okne". Rovnako nesmie obsahovať diakritiku.
  • Title: titulok aktivity zobrazovaný koncovému používateľovi.
  • Menu Resource Name: názov XML súboru s definíciou menu v lište Action Bar.

Výsledok

Po dobehnutí sprievodcu vytvorí Android Studio kostru aplikácie, s ukážkovou aktivitou.

Štruktúra projektu

Na prvý pohľad vyzerá kostra projektu komplikovane: vidno viacero adresárov (res, java) s mnohými položkami. Jadro projektu je však jednoduché, stačí jednotlivým položkám priradiť význam.

Aktivita a jej rozloženie

Ako sme už spomenuli, aktivita predstavuje okno, či formulár alebo obrazovku známu z „veľkých“ aplikácií. V rámci projektu pozostáva aktivita z dvoch zložiek: z súboru rozloženia (layout file) vo formáte XML, ktorý definuje vizuál a rozloženie prvkov aktivity a zo skamaráteného Java kódu, ktorý aktivitu oživí.

Layout aktivity

Layouty aktivít reprezentované súbormi XML sa nachádzajú v priečinku res/layout. V kostre appky vidíme ukážkový súbor activity_main.xml:

XML vyzerá na prvý pohľad divne, ak ste s ním nikdy nepracovali, o to viac, že využíva niektoré zriedkavejšie koncepty (napr. menné priestory). Uľahčíte si život, ak si prečítate niektorý z tutoriálov k písaniu XML.

<RelativeLayout
    xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <TextView android:text="@string/hello_world" android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

V layoutovom súbore vidíme dva elementy: komponent pre statický text (niekde nazývaný label) reprezentovaný XML značkou <TextView>, obsiahnutý v elemente
<RelativeLayout> čo je štandardný algoritmus pre automatické rozkladanie komponentov po displeji.

Widgety

Widget je základný komponent používateľského rozhrania. Android dáva k dispozícii mnohé pestré komponenty, či už klasické tlačidlá, textové políčka, rozbaľovacie zoznamy, ale aj moderné karty, posuvné zoznamy.

Každý widget má svoj názov v XML súbore layoutu, s ktorým je spárovaná trieda v jazyku Java.

Layout

Ak navrhujeme používateľské rozhranie, widgety neukladáme na presné pixlové pozície, ale rozložíme ich po displeji s využitím layoutov, teda algoritmov, ktoré na základe rôznych pravidiel automaticky navrhnú vhodný vzhľad.

Vďaka layoutom vieme naškálovať widgety na ľubovoľné rozmery displeja: od hodiniek až po televízory.

V Androide existuje štandardný relatívny layout, ktorý rozkladá widgety podľa pravidiel v duchu „nech tlačidlo OK je pod textovým políčkom s heslom“. Neskôr si ukážeme aj lineárny layout, ktorý ukladá widgety buď pod seba alebo vedľa seba, či mriežkový layout, ktorý ukladá widgety do tabuľky.

Čo s demonštračnou aktivitou?

O chvíľu si navrhneme svoj vlastný layout, pretože ukážka je síce pekná, ale pre našu aplikáciu nevhodná.

Aktivita a jej kód

Oživenie aktivity je reprezentovaná kódom v Jave. Android Studio nám predgenerovalo vzorovú aktivitu.

V celom materiáli sa predpokladá, že ovládate základy programovania v Jave. Ak nie, jestvuje množstvo online i offline materiálov či kurzov, ktoré vám ukážu správnu cestu.

Kód vyzerá nasledovne:

package sk.upjs.ics.android.kocka;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;


public class MainActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Vidíme, že aktivita je trieda dediaca od ActionBarActivity, ktorá prekrýva niektoré z jej metód. V ukážke máme nagenerované tri metódy, ale namiesto ich vysvetľovania urobíme trik: ponecháme len onCreate() a ostatné dve metódy zmažeme.

Tieto dve metódy slúžia na obsluhu lišty akcií, ktorú v našej aplikácii nebudeme potrebovať.

Metóda onCreate()

Aktivita sa tak zredukuje na:

public class MainActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

Metóda onCreate() predstavuje analógiu konštruktora triedy, v ktorej sa očakáva jej inicializácia. V tomto prípade sa zavolá rodičovská implementácia, nasledovaná inicializáciou layoutu.

Metóda onCreate() je prvá z metód životného cyklu aktivity, s ktorou sa stretneme. Životný cyklus je veľmi dôležitý pre korektné chovanie aktivity, pretože jeho nedodržanie povedie buď k padajúcej alebo neslušnej appke.

Metóda setContentView(), resources a prepojenie XML a kódu

Volanie setContentView() je miestom, kde sa magicky načíta XML súbor s layoutom a widgety sa sprístupnia kódu. Čo však znamená podivná konštrukcia R.layout.activity_main?

setContentView(R.layout.activity_main);

Namiesto toho, aby ste v aktivite pátrali po akejsi ceste v súborovom systéme, z ktorej načítate XML, sa používa skvelá finta.

V každej aplikácii existuje špeciálna trieda s ľúbezným názvom R, ktorá sprístupňuje resources, teda všakovaké prostriedky aplikácie, ktoré netvoria jej kód. Medzi resources patrí rozloženie aktivít, prípadné obrázky, zvuky alebo pomocné súbory.

Vďaka triede R sa nikdy nepomýlite pri uvádzaní názvu, pretože máte k dispozícii automatické dopĺňanie kódu.

V našom prípade znamená konštrukcia R.layout.activity_main odkaz na layoutový súbor s názvom activity_main.xml. Metóda setContentView ho automaticky nájde a spracuje, bez toho, aby sme pohli prstom.

Aktivita a súbory R

Načítanie XML súboru sa často nazýva inflating (nafúknutie), pretože z optimalizovaného binárneho súboru sa nafúkne, ako balón, XML súbor, a z neho jednotlivé widgety.

Manifest

Manifest je základný konfiguračný a popisný súbor aplikácie. Obsahuje názov aplikácie, názov základného balíčka, odkaz na ikonu a najmä zoznam konštrukčných prvkov: vidno v ňom najmä zoznam aktivít,

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="sk.upjs.ics.android.kocka" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

K manifestu sa budeme priebežne vracať. Zatiaľ vidíme, že obsahuje jedinú aktivitu, kde je uvedený názov triedy (.MainActivity) a niektoré ďalšie špecifiká.

Míľnik: spustenie aplikácie ====================´====== Ešte sme nenaprogramovali ani riadok kódu, ale už teraz môžeme spustiť aplikáciu. Samozrejme, nemá nič spoločné s cieľom, ale vieme si overiť korektnú funkčnosť Android Studia a prepojenie s emulátorom.

Nezabudnite si nainštalovať alternatívny emulátor Genymotion a prepojiť ho s Android Studiom.

Programovanie kocky

Keď sme pochopili základné princípy aplikácie, dopracujme kocku, teda navrhnime používateľské rozhranie a doprogramujme obslužný kód.

Rozloženie widgetov

...či skôr rozloženie widgetu. Používateľské rozhranie bude pozostávať z jediného widgetu typu tlačidlo, na ktoré sa bude dať nielen klikať, ale aj pozerať výsledok.

V layoutovom súbore odstráňme ukážkový TextView a namiesto neho vložme tlačidlo reprezentované elementom <Button>.

<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <Button
        android:id="@+id/diceButton"
        android:layout_width="144dp"
        android:layout_height="144dp"
        android:text="Roll"
        android:onClick="onDiceButtonClick"

        android:background="@android:color/holo_blue_dark"
        android:textColor="@android:color/white"
        android:textSize="28sp"
        android:gravity="center"

        android:layout_centerInParent="true"
        />



</RelativeLayout>

V tlačidle nastavme nasledovné vlastnosti reprezentované atribútmi v XML, ktoré patria do menného priestoru s prefixom android.

Identifikátor widgetu

Widget je vo väčšine prípadov povinný uviesť svoj textový identifikátor, čo platí najmä v prípade, že sa naň chceme odkázať v kóde. Má špecifický formát:

@+id/diceButton

Čítame to zľava doprava: @ znamená odkaz, či referenciu na identifikátor, kde za lomkou uvedieme ľubovoľný reťazec. A čo znamená plus? Určuje deklaráciu identifikátora, pretože bez nej by si kompilátor myslel, že sa odkazujeme na existujúci identifikátor, čo nie je pravda.

Identifikátory widgetov sú totiž unikátne vzhľadom na celý projekt. Je preto vhodné ustanoviť konzistentnú mennú konvenciu. V praxi vidieť príklady:

  • diceButton: predpona popisuje názov, prípona určuje typ widgetu
  • button_dice: použité sú malé písmená s podtržníkom s vymeneným poradím
  • btnDice: stará konvencia z Delphi využívajúca prefixy pre názvy komponentov.

Rozmery widgetov

Atribúty android:layout_width a android:layout_height udávajú šírku a výšku tlačidla v rámci aktivity. V tomto prípade využijeme odhadnutý rozmer 144 dp, čo sú zhruba 3 centimetre.

Merná jednotku dp v Androide reprezentuje device independent pixel teda bod nezávislý od hustoty displeja zariadenia. Na zariadení s hustotou 160dpi je 1 dp rovnako veľký ako 1 px, a na odlišných hustotách sa veľkosť primerane naškáluje. Ak potrebujete definovať veľkosť ľubovoľného komponentu, dp je prakticky jediná odporúčaná merná jednotka, ktorý zaručí približne rovnakú veľkosť na všetkých zariadeniach. Typicky platí, že 48 dp má na zariadení veľkosť 7--10 mm, čo je najmenšia odporúčaná veľkosť, na ktorú dokáže používateľský prst ešte rozumne ťapnúť.

Viac informácií o merných jednotkách možno nájsť na StackOverflow.com.

V iných situáciách sa využíva konštanta match_parent, ktoré znamenajú, že rozmer má vyplniť celú šírku, resp. výšku rodiča.

Alternatívnou konštantou je wrap_content, ktorá nastaví primeraný rozmer, teda štandardnú výšku, alebo šírku dostatočnú na zobrazenie textu a podobne.

Text na tlačidle

Text na tlačidle je jednoduchý: využijeme atribút text.

android:text="Roll"

Obsluha kliku na tlačidlo

Atribút onClick predstavuje názov metódy v Java kóde, ktorá sa zavolá po kliknutí na tlačidlo. V našom prípade, kde sme určili android:onClick="onDiceButtonClick" sa čaká, že v kóde bude metóda s nasledovnou hlavičkou:

public void onDiceButtonClick(View v)

V rámci tejto metódy obslúžime kliknutie na tlačidlo, ale o tom o chvíľu.

Farba pozadia a textu

Hodnota android:background nastaví farbu pozadia tlačidla. V tomto prípade využijeme odkaz na systémový resource. Android totiž definuje viacero konštánt (nielen pre farby), ktoré môžeme využiť. Reťazec @android:color/holo_blue_dark čítame zľava doprava nasledovne: @ hovorí, že ide o odkaz (referenciu), android indikuje systémovú konštantu. color reprezentuje resource typu farba a text za lomkou je systémový názov farby tmavomodrá z palety nazvanej Holo.

Farba popredia (textu) sa riadi podobnou filozofiou. V tomto prípade využijeme systémový odkaz na bielu farbu.

Android Studio ponúka automatické dopĺňanie vrátane náhľadu konkrétnej farby.

Veľkosť textu

Veľkosť textu môžeme nastaviť atribútom textSize. U nás sme zvolili veľkosť 28 sp.

Merná jednotka sp reprezentuje scaled point, čo je veľkosť, ktorá je približne rovnaká na každom zariadení bez ohľadu na rozlíšenie displeja, jeho hustotu či fyzické rozmery, ale berie do úvahy systémové nastavenie veľkosti písma. Veľkosť písma v Androide sa vždy odporúča uvádzať v sp.

Centrovanie textu

Atribút gravity pomerne prekvapivo nastavuje priťahovanie (gravitáciu) obsahu tlačidla, teda textu, k okrajom. Nastavená hodnota center umiestni text do vodorovného i zvislého stredu.

Nepleťme si atribút gravity s podobným layout_gravity, ktorý centruje widget v rámci rodičovského layoutu.

Centrovanie tlačidla

Ak nastavíme android:layout_centerInParent="true", vycentrujeme widget v rodičovi.

Obslužný kód

Vrhnime sa na úpravu hlavnej aktivity. Plán práce bude jednoduchý

  1. ak používateľ klikne na tlačidlo, zavolá sa metóda onDiceButtonClick(), ktorú musíme doimplementovať.
  2. v nej vygenerujeme náhodné číslo, čím napodobníme hod kockou
  3. text tlačidla zmeníme na číslo, ktoré sme vygenerovali.

Generovanie náhodného čísla

Na hod kockou vytvoríme pomocnú metódu, ktorá využíva klasickú javácku triedu java.util.Random a jej metódu nextInt(), ktorou vrátime číslo medzi nulou a parametrom.

private int getRandomRoll() {
    Random random = new Random();
    return random.nextInt(5) + 1; // (0--5) + 1 = 1--6
}

Práca s tlačidlom

Ak chceme nastavovať text tlačidla, potrebujeme mať objekt, na ktorom to dosiahneme. A ako sme spomínali pri layoutových súboroch, každý widget je reprezentovaný jednak elementom v XML a jednak triedou v jazyku Java. V prípade tlačidla to znamená, že máme element <Button> a triedu android.widget.Button. Odkiaľ získame objekt pre toto tlačidlo?

Vieme, že metóda setContentView() nafukuje XML súbor a spracováva ho. Presnejšie to znamená, že za oponou sa nafúkne XML do hierarchie widgetov, ktorá sa dostane pod správu objektu aktivity. Získať ľubovoľný hotový, nafúknutý a nakonfigurovaný objekt z tejto hierarchie môžeme volaním metódy findViewById()

Metóda findViewById().

Metóda findViewById() očakáva identifikátor widgetu a vráti jeho objekt. Identifikátor je kvôli korektnosti reprezentovaný premennou v resource triede R a vracia Object, ktorý následne pretypujeme na korektný typ widgetu.

Inicializácia widgetov

Medzi androiďácky folklór patrí inicializácia widgetov v metóde onCreate(). Všetky widgety, ktoré chceme využívať v kóde, vytiahneme pomocou metódy findViewById() a priradíme ich do inštančných premenných. Ukážka pre náš kód:

public class MainActivity extends ActionBarActivity {

    private Button diceButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        diceButton = (Button) findViewById(R.id.diceButton);
    }

    ...
}

Volanie metódy findViewById() je pri rozsiahlych mnohowidgetových aktivitách výpočtovo náročné. To je dôvod, prečo sa deje raz, pri vytvorení inštancie aktivity, teda v metóde onCreate().

Nastavenie textu

Nastavenie textu je potom malina. Dopracujeme očakávanú metódu pre obsluhu kliku tlačidla, získame náhodný hod a text na tlačidle určíme cez metódu setText(). Samozrejme, Java je silne typovaný jazyk, čo je dôvod, prečo musíme previesť číslo na reťazec pomocou folklórnej metódy Integer.toString().

public void onDiceButtonClick(View view) {
    int randomRoll = getRandomRoll();
    diceButton.setText(Integer.toString(randomRoll));
}

Výsledný kód aktivity

Kompletný kód aktivity len zhŕňa, čo sme vytvorili

package sk.upjs.ics.android.kocka;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.*;
import android.widget.Button;

import java.util.Random;

public class MainActivity extends ActionBarActivity {

    private Button diceButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        diceButton = (Button) findViewById(R.id.diceButton);
    }

    public void onDiceButtonClick(View view) {
        int randomRoll = getRandomRoll();
        diceButton.setText(Integer.toString(randomRoll));
    }

    private int getRandomRoll() {
        Random random = new Random();
        return random.nextInt(5) + 1; // (0--5) + 1 = 1--6
    }
}

Míľnik: hotová aplikácia

Aplikácia je hotová a môžeme ju spustiť! Klikajte na tlačidlo a sledujte, ako sa číslo náhodne mení.

Ako ďalej?

Blahoželám, vaša prvá aplikácia je hotová! Čo s ňou ďalej?

  1. Môžete ju nainštalovať do vášho telefónu. V adresári projektu, v podadresári \app\build\outputs\apk\app-debug.apk sa nachádza súbor .apk reprezentujúci hotovú aplikáciu, ktorý môžete cez USB kábel napchať do telefónu, alebo zavesiť na web a nechať ho stiahnuť používateľmi. Tento postup však vyžaduje vypnutie nepodpísaných zdrojov aplikácií.
  2. Môžete ju zverejniť na Google Play. Tento postup vyžaduje založenie konta na Google Play, jednorazovú investíciu $25 a vygenerovanie podpísanej aplikácie. Podrobnosti nájdete v oficiálnej dokumentácii Google.