- Plán a koncepty, ktoré sa naučíme
- Fotografie a video (Camera API)
- Fotoaparát a Android API
- Používanie fotoaparátu
- Riešenie do praxe
- Použitie AndroidCameraUtil
- Určovanie polohy (GPS)
- Prístup ku GPS
- Rozličné presnosti polohy
- Zdieľanie dát medzi aplikáciami
- Vlastnosti intentu pre zdieľanie
- Ukážkový kód pre odosielanie
- Prijímanie intentov
- Získanie intentu v aktivite
- Karty (tabs) a navigačný zasúvací panel (navigation drawer)
- Karty
- Filozofia
- Získame inštanciu lišty akcií
- Konfigurácia kariet
- Konfigurácia poslucháča TabListenera
- Navigačný panel (Navigation Drawer)
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
- Vytvoríme vlastný projekt, v ktorom zdemonštrujeme fotenie
- Importneme
AndroidCameraUtil
ako knižnicu- riadime sa inštrukciami z Využitie knižnice v našom projekte
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 }
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 polohyLocationListener
- 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
- údaje o polohe dostaneme v objekte typu
onProviderEnabled()
: ak sa používateľom zapne/povolí vyžadovaný provideronProviderDisabled()
: ak sa používateľom vypne/zakáže vyžadovaný provideronStatusChanged()
: 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
- LocationManager.NETWORK_PROVIDER
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 prefixomcontent://
odkazuje na content provider
- tradične
- 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
- definovať v manifeste
<intent-filter>
- v aktivite získať intent a z neho dáta
- 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
- viď dizajnérske odporúčania.
- téma o kartách k návrhu GUI
- sú k dispozícii aj v knižnici kompatibility v7
Filozofia
- získame inštanciu lišty akcií
- vytvoríme inštanciu karty
- priradíme k nej poslucháča zmeny výberu aktívnej karty
- 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 komponentuonTabReselected
: 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:
- 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
- v tom prípade vytvoríme jej inštanciu dynamicky cez
- 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
- ešte nie je vybratá žiadna karta:
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
- Dizajn GUI @ developer.android.com
- Implementácia vrátane príkladu @ developer.android.com
- pozri tiež oficiálny príklad