180 minút s Androidom: 11. stretnutie [Letom svetom]

Plán a koncepty, ktoré sa naučíme

  • používanie fotoaparátu (Camera API)
  • využívanie lokácie (GPS)
  • zdieľanie dát medzi aplikáciami
  • demonštrácia kariet (tabs) a navigačnej lišty (navigation drawer)

Fotografie a video (Camera API)

Fotoaparát a Android API

  • takmer každé zariadenie má fotoaparát
    • neraz aj dva (zadný a predný)
    • alebo aj viac (pozri HTC EVO 3D, HTC One M8)
    • alebo aj nula (Google Nexus 7 2012)
    • zároveň umožňuje používať videokameru

Používanie fotoaparátu

  • s využitím intentu:
    • ak stačí cvaknúť fotku pomocou zabudovanej aplikácie
    • viď dokumentácia
    • zdanlivo jednoduché, nevyžaduje takmer žiadnu mohutnú implementáciu
    • ale zabugované a nespoľahlivé krížom cez zariadenia
  • s využitím kompletného API:
    • komplexné náhľady
    • vlastné GUI
    • vlastné transformácie
    • komplexná implementácia, vyžaduje znalosť životného cyklu aktivity

Riešenie do praxe

Existujú dve knižnice, ktoré obchádzajú limitácie spôsobené chybami či nekonzistentnosťami medzi platformami:

  • AndroidCameraUtil
    • jednoduché API pre fotenie
    • použijeme ho v ukážke
  • CWAC-Camera
    • od autora Busy Coder’s Guide to Android
    • pokrýva množstvo vedľajších situácií
    • ak chceme programovať pre API Level 10 a nižšie, potrebujeme zaviesť aj projekt ActionBar Sherlock

Použitie AndroidCameraUtil

Príprava

  • z GitHubu stiahneme ZIP projektu
  • rozbalíme do adresára mimo eclipsáckeho workspace
  • v Eclipse importneme cez File | Import… | Existing Android Project…
  • vznikne tak nový projekt v eclipsáckom workspace
  • uistíme sa, že:
    • projekt sa importol správne
    • v project properties je knižnica označená ako Library (sekcia Android)

Vlastný projekt

  1. Vytvoríme vlastný projekt, v ktorom zdemonštrujeme fotenie
  2. Importneme AndroidCameraUtil ako knižnicu
  3. Hlavnú aktivitu v našom projekte oddedíme od TakePhotoActivity:

    package com.example.photor;
    
    import de.ecotastic.android.camerautil.example.TakePhotoActivity;
    
    public class MainActivity extends TakePhotoActivity {
        // tu nic nie je    
    }
    
  4. spustíme projekt a obdivujeme demo.

Funkcionalita vlastného projektu:

  • po kliknutí na tlačidlo sa spustí zabudovaná aplikácia pre fotoaparát
  • po uložení fotky sa vrátime do našej aktivity a uvidíme miniatúru a URL fotografie

Štúdium

Odporúčam preštudovať zdrojáky TakePhotoActivity, ktoré sú zároveň demom celej knižnice. Ukazujú, ako spustiť aktivitu fotoaparátu, ako vyzdvihnúť výsledok a ako pristupovať k nafoteným dátam pomocou bitmapy i pomocou URL adresy smerujúcej do súborového systému.

Určovanie polohy (GPS)

Android umožňuje zisťovať polohu podľa viacerých senzorov:

  • na základe aktuálne pripojenej WiFi siete
    • paranoia time: Google udržiava v systéme mapovanie medzi otvorenými WiFi adresami a lokáciou, ktoré zaslali používatelia so zapnutým GPS alebo ktoré zistilo Google auto pri fotení do Google Maps.
    • vyžaduje pripojenie k WiFi, ktoré žerie baterku
  • na základe triangulácie BTS staníc GSM operátora
    • málo presné
  • na základe GPS senzora:
    • presnosť v metroch
    • vyžaduje zapnuté GPS, ktoré žerie baterku.

Prístup ku GPS

  • LocationManager: systémová služba pre určovanie polohy
  • LocationListener
    • zaregistrujeme ho v správcovi lokácii
    • poslucháč, ktorý je notifikovaný v prípade zmeny polohy

Ukážka kódu

LocationManager locationManager 
  = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

LocationListener locationListener = new MyLocationListener();

locationManager.requestLocationUpdates(
   LocationManager.GPS_PROVIDER, 60 * 1000, 0, locationListener);
  • Pomocou getSystemService() získame inštanciu systémovej služby:
    • toto sme robili viackrát: pri získavaní broadcast managera, či alarm service.
  • vytvoríme inštanciu triedy implementujúcej LocationListener
  • zapneme vyžadovanie aktualizácií o polohe cez requestLocationUpdates()
    • GPS_PROVIDER znamená využívanie presnej GPS polohy. Pozor, aplikácia potrebuje oprávnenie (permission) android.permission.ACCESS_FINE_LOCATION!
    • druhý parameter udáva periódu aktualizácií v milisekundách. Treba byť slušný k baterke!
    • druhý parameter udáva vzdialenosť v metroch, ktorá vyvolá aktualizáciu. Ak nepotrebujeme zmenu po metroch, ktorá je náročná na baterku, uveďme nulu

Poslucháč zmien polohy

Poslucháč má štyri metódy:

  • onLocationChanged(): zavolaná, ak sa zmenila poloha.
    • údaje o polohe dostaneme v objekte typu Location
    • vieme zistiť zemepisnú šírku (latitude) zodpovedajúcu rovnobežke
    • zemepisnú dĺžku (longitude) zodpovedajúca poludníku
    • vzdialenosť od inej lokácie (distance)
    • prípadne výšku (altitude)
    • alebo azimut (bearing), t. j. uhol od severu
  • onProviderEnabled(): ak sa používateľom zapne/povolí vyžadovaný provider
  • onProviderDisabled(): ak sa používateľom vypne/zakáže vyžadovaný provider
  • onStatusChanged(): ak sa zmení stav poskytovateľa, napríklad sa vyradí zo služby, či dočasne “vypadne”

Formátovanie polohy

String latitude = location.convert(location.getLatitude(), Location.FORMAT_SECONDS);

Zistenie vzdialenosti v metroch medzi dvoma polohami

Location doma = ...
Location skola = ...
double vzdialenostVMetroch = doma.distanceTo(skola);

Zistenie vzdialenosti a azimutu (WTF time)

Location.distanceBetween(lat1, long1, lat2, long2, result)
  • statická metóda naplní zadané pole 1 – 3 hodnotami
  • vzdialenosť, počiatočný azimut, koncový azimut

Rozličné presnosti polohy

  • LocationProvider: trieda, ktorá dokáže vyhľadať polohu konkrétnym spôsobom
  • jednotliví provideri sa líšia
    • presnosťou
    • požiadavkami na pripojenie
    • spoplatnením
  • implicitné možnosti:
    • LocationManager.NETWORK_PROVIDER
      • využíva BTS a WiFi
    • LocationManager.GPS_PROVIDER
      • GPS
  • môžeme vyhľadať najlepšieho providera vzhľadom na podmienky:

    // najlepší operátor, ktorý nevyžaduje spoplatnenie
    Criteria c = new Criteria();
    c.setCostAllowed(false);
    String najlepsiProvider = locationManager.getBestProvider(c, true);
    locationManager.requestLocationUpdates(najlepsiProvider, 0, 0, locationListener);
    

Zdieľanie dát medzi aplikáciami

  • v Androide existuje jednoduchý mechanizmus výmeny dát medzi aplikáciami pomocou implicitných intentov
  • tie sú rozoslané do systému a komponenty (typicky: aktivity), ktorých intent filtre dokážu prijať danú správu, ju potom spracujú.

Vlastnosti intentu pre zdieľanie

  • akcia (action): vždy nastavená na ACTION_SEND.
  • dáta (data): obsahuje Uri reprezentujúce adresu dát, s ktorými sa pracuje:
    • tradične Uri s prefixom content:// odkazuje na content provider
  • MIME typ: udáva typ dát, s ktorými sa pracuje
  • kategória (category): dodatočná informácia o intente. Obvykle nie je potrebná.
  • extras: intent sa správa ako hashmapa a v extras môže niesť ľubovoľné dodatočné informácie.

  • vďaka intentom môžeme zdieľať dáta (share)

Ukážkový kód pre odosielanie

Intent intent = new Intent(Intent.ACTION_SEND)
    .setType(HTTP.PLAIN_TEXT_TYPE) // text/plain
    .putExtra(Intent.EXTRA_TEXT, "Drop it!");

// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(sendIntent);
}

V systéme sa nájdu všetky aktivity, ktoré dokážu spracovať takýto intent a následne:

  • ak existuje jediná aktivita, ktorá to vie spraviť, otvorí sa
  • alebo sa objaví dialógové okno s výberom aktivity, ktorá ho spracuje:
    • v štandardnom Androide dostaneme na výber poslať text ako mail alebo ako SMS
  • alebo sa použije tá aktivita, ktorú používateľ nastavil ako predvolenú (Android 4.x: Use Always)

    Poznámka: rušenie prednastavených aktivít

    Prednastavenú aplikáciu môžeme zrušiť v systémových nastaveniach, nájdeme aplikáciu a použitím Clear Defaults vypneme prednastavené spracovávanie intentov.

Ak chceme vždy vyvolať dialógové okno, použime:

Intent.createChooser(intent, title);

Pozri tiež dokumentáciu k intentom.

Bonus: zdieľanie na Facebook

  • Zdroj: post na StackOverflow.
  • odkaz určený na stenu posielame v extra EXTRA_TEXT.

    String urlToShare = "http://stackoverflow.com/questions/7545254";
    Intent intent = new Intent(Intent.ACTION_SEND);
    intent.setType(HTTP.PLAIN_TEXT_TYPE);       
    intent.putExtra(Intent.EXTRA_TEXT, urlToShare);
    

Prijímanie intentov

  1. definovať v manifeste <intent-filter>
  2. v aktivite získať intent a z neho dáta
  3. urobiť, čo je následne potrebné

Manifest

V elemente <activity> definujme <intent-filter>:

<intent-filter>
    <action android:name="android.intent.action.SEND" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="text/*" />
</intent-filter>

Potrebujeme definovať:

  • akciu: v tomto prípade android.intent.action.SEND
  • kategóriu: pre implicitné intenty sme povinní definovať kategóriu, aj keby mala byť defaultná (viď dokumentácia]
  • definujeme tiež akceptovateľný MIME typ: buď doslova, alebo pomocou zástupného znaku *.

Získanie intentu v aktivite

Intent intent = getIntent();
if(Intent.ACTION_SEND.equals(intent.getAction())
        && intent.getType().startsWith("text/")) {

    String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
    // spracujme text
}
  • intent v aktivite získame cez getIntent()
  • musíme ešte overiť, či spracovávame správny intent
    • filter intentov síce zablokuje nepovolené intenty, ale keďže hlavná aktivita môže byť spustená i z launcheru, i implicitným intentom, musíme odlíšiť jednotlivé prípady
  • vytiahneme príslušné extra a spracujeme ho.

Karty (tabs) a navigačný zasúvací panel (navigation drawer)

  • Android 4.x zmenil pohľad na návrh GUI
  • pribudli nové komponenty:
    • action bar
    • karty (tabs)
    • navigačný zasúvací panel (navigation drawer)
  • podrobnosti pozri video z Google IO 2013

Karty

android-tab-demo

Filozofia

  1. získame inštanciu lišty akcií
  2. vytvoríme inštanciu karty
  3. priradíme k nej poslucháča zmeny výberu aktívnej karty
  4. kartu pridáme do lišty akcií

Všetky akcie sa udejú v onCreate() v aktivite typu ActionBarActivity.

Získame inštanciu lišty akcií

// setup action bar for tabs
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
actionBar.setDisplayShowTitleEnabled(false);
  • Po nastavení nastavíme režim akcie na “kartový” (NAVIGATION_MODE_TABS)
  • vypneme zobrazovanie titulku aktivity v jej hornej časti

Konfigurácia kariet

Tab tab = actionBar
        .newTab()
        .setText("Random quote")
        .setTabListener(this)
        .setTag(QuoteFragment.class.getName());
actionBar.addTab(tab);
  • vytvoríme nový objekt karty
  • nastavíme mu titulok
  • priradíme mu poslucháča: bude ním trieda implementujúca TabListener, ktorou bude, zhodou okolností, trieda našej aktivity
  • nastavíme tag, čo je ľubovoľný objekt priraditeľný k objektu karty. Keďže budeme musieť vytvárať inštancie triedy fragmentov, do tagu si poznačíme názov triedy

Konfigurácia poslucháča TabListenera

Necháme našu aktivitu implementovať interfejs TabListener, ktorá sa má postarať o zámenu obsahu v jednotlivých kartách. Samozrejme, využijeme fragmenty.

Interfejs má tri metódy:

  • onTabSelected: zavolaná pri výbere karty. Tu potrebujeme vytvoriť alebo obnoviť fragment a zobraziť ho.
  • onTabUnselected: zavolaná pri zrušení výberu karty. Tu potrebujeme odpojiť existujúci fragment od aktuálneho komponentu
  • onTabReselected: ak sa vyberie už existujúca karta. Tu sa obvykle nedeje nič.

onTabSelected: výber karty

@Override
public void onTabSelected(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);
    }       
}
  • z karty vytiahneme tag obsahujúci názov triedy fragmentu, ktorá sa má použiť
  • máme dva prípady:
    1. ešte nie je vybratá žiadna karta:
      • v tom prípade vytvoríme jej inštanciu dynamicky cez Fragment.instantiate()
      • pridáme ju do transakcie cez add()
        • transakciu nesmieme commitovať, urobí to za nás obsluha kariet
      • poznačíme si vybraný fragment do inštančnej premennej
    2. už je vybraná karta
      • pokúsime sa zistiť, či už existuje inštancia fragmentu a či je napojená (attached) na rodičovský kontajner
      • ak nie je, tak ju napojíme
      • ošetríme ešte špeciálny prípad, keď Android automaticky uloží a obnoví kartu pri zmene konfigurácie: inak by sa prekryl predošlý fragment, ktorý bol automaticky obnovený a nový fragment, ktorý sa práve vytvoril

onTabUnselected: zrušenie výberu karty

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
    String tag = (String) tab.getTag();
    Fragment selectedFragment = getSupportFragmentManager().findFragmentByTag(tag);
    if (selectedFragment != null) {
        ft.detach(selectedFragment);
    }
}
  • tu len získame aktívny fragment a odpojíme ho od aktivity

onTabReselected: výber existujúcej karty

@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
    // User selected the already selected tab. Usually do nothing.
}

Projekt na GitHube

Stiahnite si demonštračný projekt z GitHubu.

Navigačný panel (Navigation Drawer)

Používa sa v prípade, keď:

  • je kariet (tabov) priveľa
  • alebo má aplikácia viacero pohľadov najvyššej úrovne, ktoré nemajú mimoriadne blízky vzťah
  • alebo chceme mať v aplikácii “turistický smerovník” (navigation hub) pre rozličné aktivity
  • viď tiež dokumentácia k vhodnosti použitia

Pridaj komentár

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