Android-skolen del 5: Byg tre små robotter sammen til én stor
Velkommen til femte og sidste lektion i Version2's Android-skole.
Denne gang går vi et spadestik dybere i den måde, androidens grafiske brugergrænseflade bygges op på.
Tanken er at vise, hvordan flere selvstændige applikationer ? eller activities - kan samles i én og samme brugergrænseflade, og det gør vi ganske enkelt ved at hugge koden fra afsnit 2, 3 og 4 af Android-skolen og lægge al funktionaliteten ind i én og samme applikation, adskilt grafisk af tre faneblade.
Denne gang er det altså mere 'skelettet' uden om koden, end selve koden, der fokuseres på.
Foreløbig har vi set på forskellige typer af views og layouts. For eksempel brugte vi et ListView til at vise kontakterne i telefonbogen i Android-skolen del 2, mens Android-skolen del 3 bød på et MapView til vores Google Maps/GPS-applikation.
Under første faneblad placeres telefonbogen fra afsnit 2. Andet faneblad indeholder Google Maps/GPS-applikationen fra afsnit 3, og tredje faneblad viser applikationen fra afnit 4, der demonstrerer trådning af den grafiske brugergrænseflade.
Gæsteunderviser er denne gang atter Klaus Kartou, der er en af hovedudviklerne bag den danske koncertapplikation til Android-platformen, Gigbox.
»Tabs er generelt en god måde at samle forskellig funktionalitet i samme vindue. Det gør det nemt for brugeren at overskue flere activities og skifte imellem dem,« siger Klaus Kartou.
1) Lidt teori om GUI-design med Android
Filen R.java (ligger under projektundermappen 'gen') forbinder vores Java-kildekode med det grafiske layout af brugergrænsefladen, der specificeres i XML-filen main.xml. R.java autogenereres ud fra layoutet i main.xml, og derfor ændrer vi aldrig direkte i den.
I de foregående afsnit har vi holdt os til at definere layoutet i én og samme fil, main.xml, men da vi denne gang skal håndtere flere forskellige slags views i samme brugergrænseflade, skal der flere XML-filer på banen.
Idéen er derfor at oprette nye XML-filer til hvert af de tre faneblade i applikationen. Vi ender derfor med filerne:
- main.xml: Indeholder det overordnede faneblad-layout.
- phonebook.xml: Indeholder layoutet af telefonbog-applikationen
- mapview.xml: Indeholder layoutet af Google Maps/GPS-applikationen
- progressbar.xml: Indeholder layoutet af trådning-applikationen, der viser en progressbar.
De største ændringer sker i main.xml, idet de andre XML-filer blot indeholder layoutet som angivet i de respektive afsnit af Android-skolen.
2) Opret projekt og klasse-filer
Start Eclipse.
Opret et nyt Android-projekt, ganske som du plejer. Navngiv applikationen som vist nedenfor, eller efter eget valg.

Bemærk, at du skal vælge Google API'et i version 1.6, da faneblad nummer to som nævnt indeholder Google Maps-applikationen.
Du har nu oprettet projektet med selve hovedklassen AndroidSkolenDel5, som er ansvarlig for at oprette faneblade og lægge applikationerne fra afsnit 2, 3 og 4 ind under dem.
Indtil videre indeholder AndroidSkolenDel5 blot en autogenereret onCreate()-metode, og vi lader den stå sådan et øjeblik.
Nu skal du oprette de klasser, der indeholder koden til de tre foregående applikationer. Opret filerne
- MyPhoneBook.java
- MyMap.java
- MyProgressBar.java
og sørg for, at de ligger placeret samme sted som AndroidSkolenDel5.java (underbiblioteket 'src').
Herefter tager du 'indmaden' fra klasserne i de foregående afsnit af Android-skoeln og kopierer koden ind i de nyoprettede filer efter fremgangsmåden:
- AndroidSkolenDel2.java -> MyPhoneBook.java
- AndroidSkolenDel3.java -> MyMap.java
- AndroidSkolenDel4.java -> MyProgressBar.java
Husk at sætte de nye klasser til arve fra de samme klasser og interfaces, som var tilfældet i de 'gamle' klasser.
- MyPhoneBook.java: extends ListActivity
- MyMap.java: extends MapActivity implements LocationListener
- MyProgressBar.java: extends Activity
Dermed ser eksempelvis MyMap.java nu sådan ud (forkortet i bunden):
01 **public class **MyMap **extends **MapActivity **implements **LocationListener {
02 **private **Location location;
03 **private **MapView mapView;
04 **private **MapController mc;
05 **private **LocationManager locationManager;
06
07 **public ****void **onCreate(Bundle savedInstanceState) {
08 **super**.onCreate(savedInstanceState);
09 setContentView(R.layout.main);
10
11 mapView = (MapView) findViewById(R.id.mapview1);
12 mapView.setBuiltInZoomControls(**true**);
13 mc = mapView.getController();
14 }
15
16 **protected ****void **onResume() {
17 List providers;
18 **super**.onResume();
19 ...
|
3) Definer layouts i XML-filer
Som nævnt tidligere skal du oprette nye XML-filer til hver af de tre activities, der skal vises i hver deres faneblad.
- main.xml: Indeholder det overordnede faneblad-layout.
- phonebook.xml: Indeholder layoutet af telefonbog-applikationen
- mapview.xml: Indeholder layoutet af Google Maps/GPS-applikationen
- progressbar.xml: Indeholder layoutet af trådning-applikationen, der viser en progressbar.
Main.xml er oprettet til os på forhånd. Den lader vi ligge et øjeblik.
Vi går videre med at kopiere layoutet fra filen main.xml i projekterne Android-skolen del 2, 3 og 4 ind i tre nye XML-filer.
- phonebook.xml
- mapview.xml
- progressbar.xml
Filerne oprettes i Eclipse ved at taste ctrl+N og vælge Android XML File under punktet Android.
XML-filerne ser nu ud som følger:
- phonebook.xml:
01 <?xml version="1.0" encoding="utf-8"?>
02 "http://schemas.android.com/apk/res/android"
03 android:orientation="vertical" android:layout_width="fill_parent"
04 android:layout_height="fill_parent">
05 "@id/android:list" android:layout_width="fill_parent"
06 android:layout_height="fill_parent" android:layout_weight="1" />
07 "@+id/name" android:textSize="18px"
08 android:textStyle="bold" android:layout_width="fill_parent"
09 android:layout_height="wrap_content" />
10 "@+id/phone_number" android:layout_width="fill_parent"
11 android:layout_height="wrap_content" />
12
|
Læg her mærke til, at vi har tilføjet et ListView-tag til phonebook.xml i linje 5-6, da vi ellers får en fejl på runtime.
- mapview.xml:
01 <?xml version="1.0" encoding="utf-8"?>
02 "http://schemas.android.com/apk/res/android"
03 android:orientation="vertical" android:layout_width="fill_parent"
04 android:layout_height="fill_parent">
05
06
07 android:id="@+id/mapview1" android:layout_width="fill_parent"
08 android:layout_height="fill_parent" android:enabled="true"
09 android:clickable="true" android:apiKey="0Svjiran6ZYsb5ENaZ-B-_ZNl5p65sLmX87VuSQ" />
10
|
- progressbar.xml:
01 <?xml version="1.0" encoding="utf-8"?>
02 "http://schemas.android.com/apk/res/android"
03 android:orientation="vertical" android:layout_width="fill_parent"
04 android:layout_height="fill_parent">
05 "@+id/my_button" android:layout_width="100px"
06 android:layout_height="wrap_content" android:text="foo" />
07 "@+id/progress_bar"
08 android:layout_width="fill_parent" android:layout_height="wrap_content"
09 style="?android:attr/progressBarStyleHorizontal" />
10
|
Nu har du oprettet XML-filerne og skal derefter blot bede Java-filerne om at hente layoutet derfra, i stedet for i main.xml.
Det gøres ganske enkelt ved at erstatte metodekaldet setContentView(R.layout.main) i linje to af onCreate()-metoden i de tre filer MyPhoneBook.java, MyMap.java og MyProgressBar.java med henholdsvis
- MyPhoneBook.java: setContentView(R.layout.phonebook)
- MyMap.java: setContentView(R.layout.mapview)
- MyProgressBar.java: setContentView(R.layout.progressbar)
Bemærk, at du i MyPhoneBook.java også skal ændre i metodekaldet
cursorAdapter = new SimpleCursorAdapter(this, R.layout.main, c, columns, contacts);
nederst i onCreate()-metoden. Her skal andet argument, R.layout.main, også ændres til R.layout.phonebook.
4) Opret fanebladene
Nu har du oprettet tre nye klasser, som definerer de activities, vi gerne vil afvikle i de tre faneblade i vores layout, og du har samtidig skabt tre nye XML-filer, der indeholder de respektive layouts til de tre activities.
Inden vi skriver kode i selve hovedklassen, AndroidSkolenDel5.java, lægger vi ud med at definere layoutet for klassens activity i main.xml.
01 <?xml version="1.0" encoding="utf-8"?>
02 "http://schemas.android.com/apk/res/android"
03 android:id="@android:id/tabhost" android:layout_width="fill_parent"
04 android:layout_height="wrap_content" layout_weight="1">
05 "vertical"
06 android:layout_width="fill_parent" android:layout_height="fill_parent">
07 "@android:id/tabs"
08 android:layout_width="fill_parent" android:layout_height="wrap_content" />
09 "@android:id/tabcontent"
10 android:layout_width="fill_parent" android:layout_height="fill_parent">
11
12
13
|
Vi har her defineret en TabHost, der indeholder hele layoutet for vores activity. En TabHost skal indeholde en TabWidget og et FrameLayout som underelementer, og vi sørger for at layoute dem ordenligt i et LinearLayout. Framelayoutet er der, vi opbevarer det indhold, der skal være i et faneblad.
Med layoutet på plads i main.xml retter vi nu opmærksomheden mod vores hovedklasse, AndroidSkolenDel5.java. Her skal du nu oprette fanebladene og fylde dem ud med indholdet af de tre activities.
Det gøres ved at oprette en såkaldt TabHost og fortælle den, hvad du gerne vil have puttet ind i de enkelte faneblade.
Begynd med at sætte AndroidSkolenDel5.java til at arve fra TabActivity i stedet for Activity.
Dernæst fylder vi onCreate()-metoden ud med kode som følger:
01 **public class **AndroidSkolenDel5 **extends **TabActivity {
02
03 **private **TabHost mTabHost;
04
05 **public ****void **onCreate(Bundle savedInstanceState) {
06 **super**.onCreate(savedInstanceState);
07 setContentView(R.layout.main);
08
09 mTabHost = getTabHost();
10
11 Context ctx = getApplicationContext();
12
13 Intent phoneBook = **new **Intent(ctx, MyPhoneBook.**class**);
14 Intent map = **new **Intent(ctx, MyMap.**class**);
15 Intent progressBar = **new **Intent(ctx, MyProgressBar.**class**);
16
17 mTabHost.addTab(mTabHost.newTabSpec("tab_test1").setIndicator("Fane 1")
18 .setContent(phoneBook));
19 mTabHost.addTab(mTabHost.newTabSpec("tab_test2").setIndicator("Fane 2")
20 .setContent(map));
21 mTabHost.addTab(mTabHost.newTabSpec("tab_test3").setIndicator("Fane 3")
22 .setContent(progressBar));
23
24 mTabHost.setCurrentTab(0);
25 }
26 }
|
Vi tager den ét skridt ad gangen.
I linje 9 benytter vi metodekaldet getTabHost() til at hente en reference til den TabHost, vi har defineret i layoutet i main.xml.
I linje 11 henter vi den kontekst, vores applikation har lige nu, med kaldet getApplicationContext().
Det bruges som argument til de tre Intents, vi definerer i linje 13-15. Et Intent er en abstraktion over en handling, vi gerne vil udføre ? i dette tilfælde telefonbogen, kortapplikationen og trådapplikationen ? som vi efterfølgende giver med som argument til metodekaldene i linje 17-22, der tilføjer tre faneblade med indhold til vores TabHost.
»TabActivity benytter Intents til at specificere, hvilken activity der er knyttet til en given tab. Når brugeren klikker på en tab, startes den pågældende activity, og dens tilhørende vindue indsættes i TabActivitiens tab-contentview,« siger Klaus Kartou.
I linje 24 slutter vi af med at sætte det første faneblad som den aktuelle fane og dermed den første fane, brugeren ser, når applikationen startes op.
5) Sæt permissions og activities i manifestet
Sidste punkt på listen, inden vi kan køre vores nye applikation, er at sætte de permissions, vi skal bruge, i AndroidManifest.xml.
Det drejer sig om de fem uses-permission-tags vist i linje 4-10 herunder:
01 <?xml version="1.0" encoding="utf-8"?>
02 "http://schemas.android.com/apk/res/android"
03 **package**="dk.version2.as5" android:versionCode="1" android:versionName="1.0">
04 "android.permission.ACCESS_FINE_LOCATION">
05
06 "android.permission.ACCESS_COARSE_LOCATION">
07
08 "android.permission.INTERNET">
09 "android.permission.READ_CONTACTS">
10 "android.permission.CALL_PHONE">
11 "@drawable/icon" android:label="@string/app_name"
12 android:debuggable="true">
13 "com.google.android.maps" />
14 ".AndroidSkolenDel5" android:label="@string/app_name">
15 ...
|
, der giver os lov til at bruge både GPS og triangulering og tilgå dataforbindelsen til Google Maps/GPS-applikationen, samt læse kontakterne fra telefonens adressebog og ringe dem op i telefonbog-applikationen.
Samtidig har vi defineret i linje 13, at vores applikation bruger Google Maps.
Som det ser ud lige nu, har vi kun defineret AndroidSkolenDel5.java som en activity i manifestet. For at kunne afvikle de tre foregående applikationer i fanebladene, skal vi også definere dem som activities i manifestet.
Det gøres ved at tilføje tre activities efter activity'en AndroidSkolenDel5:
1 ...
2
3 ".MyPhoneBook" android:label="@string/app_name">
4
5 ".MyMap" android:label="@string/app_name">
6
7 ".MyProgressBar" android:label="@string/app_name">
8
9 ...
|
Dermed kommer det samlede manifest til at se således ud:
01 <?xml version="1.0" encoding="utf-8"?>
02 "http://schemas.android.com/apk/res/android"
03 **package**="dk.version2.as5" android:versionCode="1" android:versionName="1.0">
04 "android.permission.ACCESS_FINE_LOCATION">
05
06 "android.permission.ACCESS_COARSE_LOCATION">
07
08 "android.permission.INTERNET">
09 "android.permission.READ_CONTACTS">
10 "android.permission.CALL_PHONE">
11 "@drawable/icon" android:label="@string/app_name"
12 android:debuggable="true">
13 "com.google.android.maps" />
14 ".AndroidSkolenDel5" android:label="@string/app_name">
15
16 "android.intent.action.MAIN" />
17 "android.intent.category.LAUNCHER" />
18
19
20 ".MyPhoneBook" android:label="@string/app_name">
21
22 ".MyMap" android:label="@string/app_name">
23
24 ".MyProgressBar" android:label="@string/app_name">
25
26
27 "4" />
28
|
Gem projektet og oversæt og kør det med ctrl+b efterfulgt af ctrl+F11.
Nu kan du vælge mellem applikationerne fra de foregående afsnit af Android-skolen i de tre faneblade:

Hermed er du færdig med den femte og sidste undervisningstime i Android-skolen.
»Ønsker man at ændre radikalt på funktionaliteten eller udseendet af tabs, er det desværre ikke helt ligetil. Da TabActivity er en specialisering af klassen ActivityGroup, kan man i stedet implementere sin egen ActivityGroup. I Gigbox har vi lagt tabs i bunden og indført vores eget tab-layout netop via denne metode,« siger Klaus Kartou.

Et skræddersyet faneblad-layout i Gigbox-applikationen. Her er de tre tabs placeret i bunden af skærmbilledet med egne ikoner. (Grafik: Gigbox).
»Et godt råd er at gennemtænke layoutet af ens activity, inden man går igang. Tegn eventuelt en tegning på et stykke papir og overvej, hvilke GUI-komponenter, der kan bruges til at indfri den,« siger Klaus Kartou.
Litteratur: [1] http://developer.android.com
Skab Danmarks bedste Android-applikation og vind en eksklusiv tur med alt betalt til verdens største mobilmesse i Barcelona. Deadline er 11. januar 2010. Læs mere på http://www.version2.dk/android


Tilføj kommentar