DevTutorial 3 - De relatie tussen Views en de Activity

DevTutorial 3 - De relatie tussen Views en de Activity

Let op!

Als je vragen hebt over deze DevTutorial ('ik heb een fout bij deze stap', 'hoe doe je dit of dat in Android' etcetera), stel ze dan alsjeblieft niet in de comments onderaan deze pagina, maar doe dat hier op het forum! Omdat programmeervragen ingewikkelde vragen zijn, kunnen deze veel beter beantwoord worden op het forum: daar heb je uitgebreide mogelijkheden om je vraag te stellen en om geholpen te worden. Ik heb het er expres deze keer wat duidelijk neer gezet omdat er de afgelopen DevTutorial nog steeds een paar mensen waren die vragen in de comments plaatsen. Een leesfoutje kan gebeuren hoor! Maar ik hoop dat dat nu niet meer gebeurt. En weer bedankt voor de andere comments op de vorige DevTutorial , ik vond ze leuk om te lezen!

Wat gaan we doen

Deze DevTutorial beschrijft uitgebreid de werking achter Views (en Layouts die eigenlijk ook Views zijn). Dit is belangrijk om te weten omdat een app meestal begint met het ontwerpen van de schermen, daarna wordt pas meestal de code geschreven. Verder zijn Layouts en Views hele herkenbare begrippen voor iemand met weinig programmeerervaring: iedereen weet wat een knop, een invoerveld of een leegruimte op een scherm is. Door deze herkenbare begrippen nog even goed uit te legen kunnen we hier de komende komende DevTutorials op uitbouwen, hierdoor wordt je Androidkennis geleidelijk stevig uitgebreid. De volgende onderwerpen worden behandeld:

Zodra je die achtergrondkennis hebt gaan we bezig met een app die de volgende werkdag uitrekent op basis van een datum die je invoert met de DatePicker. Ook gebruiken we de CheckBox zodat aangegeven kan worden of zaterdagen wel of niet als werkdag gerekend moeten worden. Het resultaat is een app die voor jou een afspraak op de volgende werkdag kan plannen: . Dit gaan we in eerste instantie doen met een LinearLayout, vervolgens gebruiken we een Qualifier om een RelativeLayout-variant van de layout te laden in landschapmodus. Succes ermee en veel plezier gewenst!

De Relatie tussen Views, Layouts, Activities en R

Hoe Views, Layouts, Activities en de R class met elkaar samenwerken is een aardig op zichzelf staand stukje kennis, één van de belangrijke basisconcepten uit Android programmeren. Eerst wordt je het verschil uitgelegd tussen gewone Views en ViewGroups. Daarna wordt verder uitgelegd hoe een Activity werkt, wat zijn Methods zijn (het DNA van de Activity class) en hoe deze samenwerken met de twee belangrijkste groepen van Views. Daarna wordt je R.java uitgelegd, en hoe deze als verbindingsbrug werkt tussen Activities en Views. Daarna wordt er aan de hand van de het Class Hierarchy concept uitgelegd hoe alle Views (dus ook ViewGroups) afstammeling zijn van de View Class.

Twee belangrijke soorten Views: ViewGroups en gewone Views

Zoals in de vorige tutorial verteld is, is alles dat je ziet op een Androidscherm het resultaat van een View. Op het scherm is een View een rechthoekig vlak met een bepaalde breedte en hoogte. In dit vlak mag de View zelf bepalen wat hij laat zien.

Bijvoorbeeld bij een knop (Button) is dat een plaatje van een knop met daar bovenop een tekst geprojecteerd waardoor de gebruiker die View ziet en herkent als een knop. Bij bijvoorbeeld een tabblad houder (TabHost) deelt de view zichzelf op in twee gedeelten: (1) het bovenste gedeelte waarin alle tab-knoppen getoond worden en (2) het grote gedeelte daaronder waarin de inhoud van de verschillende tabs getoond kan worden, afhankelijk van welke tab actief is.

De Views die je het meest gebruikt, zijn op te delen in twee soorten: ViewGroups en gewone Views. Bij gewone Views moet je denken aan knoppen, selectievakjes, afbeeldingen van plaatjes, tekstvelden. De tweede soort, ViewGroups, zijn views die andere views kunnen bevatten: vandaar ook de naam ViewGroup --> Groep van Views. Wanneer je een ViewGroup gebruikt, dan reserveer je ruimte op het scherm (door de hoogte en breedte van de ViewGroup in te stellen) zodat je die ruimte vervolgens kan gebruiken om andere views op te laten zien zoals bijvoorbeeld een EditText of een Button. Dit principe waar een ViewGroup in zichzelf andere views kan bevatten noem je de layout-hiërarchie. Ter verduidelijking is hier de layout-hiërarchie van de layout van de eerste tutorial: Je ziet dat de EditText en de Button "in" de LinearLayout zitten (een LinearLayout is een typisch voorbeeld van zo'n ViewGroup). Ook als je de xml-code bekijkt van layout.xml zie je dit terug; op regel 3 wordt de openingstag van LinearLayout gebruikt, dan volgen de tags van de EditText en de Button en daarna pas op regel 25 wordt de LinearLayout gesloten: (ik heb even wat extra enters in het bestand gezet zodat hij overzichtelijk is). Deze layout-hiërarchie is nog een vrij simpele: meestal heb je meerdere ViewGroups die in elkaar zitten en dan weer hun eigen gewone Views bevatten. De manier hoe de Views gepositioneerd kunnen worden, hangt af van het type layout waar ze in zitten. In een LinearLayout worden alle views (knoppen/tekstvelden/keuzeknopje etcetera) die je erin stopt onder elkaar getoond; er worden dan geen Views naast elkaar gezet. In een RelativeLayout stel je de posities van de views in aan de hand van elkaar en de randen van de layout. In een ListView worden de views onder elkaar geladen met scrollmogelijkheden zodat de gebruiker het idee krijgt dat je door een lijstje aan het scrollen bent. Zometeen gaan we twee apps maken waarin we de LinearLayout en de RelativeLayout gebruiken. Voordat het zover is, wordt eerst nog beschreven hoe in Eclipse de Layout xml-bestanden gekoppeld worden aan de sourcecode die je schrijft in de map src.

public class MainActivity extends Activity

We gaan nu dieper in op wat een Activity is en hoe die samen kan werken met een layout. Dit gaat voornamelijk door middel van de methods die je kan gebruiken in je Activity class. Er wordt uitgelegd waar die methods vandaan komen om in de paragraaf die hierna komt uit te leggen hoe die methods inhaken op R.java, een bestand dat door de ADT (Android Developer Tools) gegenereerd wordt. Een erg belangrijk element hierin is het woordje extends: dit wordt nu uitgelegd voor je eigen class MainActivity, maar kan je ook gebruiken in (bijna) alle andere classes die je ooit schrijft. Een layout gebruik je altijd in combinatie met een Activity. Wanneer een Activity actief is, dan is die de baas over het scherm en bepaalt dan wat er op het scherm getoond moet worden. Een voorbeeld hiervan is toen je dit gebruikte in de vorige DevTutorial door de method setContentView aan te roepen in je Activity class, dit deed je bijvoorbeeld op regel 19 van de class MainActivity in de vorige DevTutorial: (open zelf ook nog even Androidproject MoneyConvert in Eclipse, ik ga het voorlopig even als voorbeeld gebruiken). Zoals in de vorige tutorial beschreven, is een method een stukje functionaliteit van een class die je afzonderlijk uit kan voeren. Dit uitvoeren kan je doen door de naam van de method op te schrijven, gevolgd door '(' en ')'. Tussen de haakjes kan je, wanneer de method daarvoor geschikt is, een extra bericht meesturen om te zorgen dat de method op een bepaalde manier wordt uitgevoerd. Dit kan je zien op regel 19 van bovenstaand screenshot wat in gewoon Nederlands het volgende betekent: "zorg ervoor dat het scherm er anders uit komt te zien: ga de layout veranderen. En ik heb er nog extra bericht bij: je moet dit doen met de layout main." Het vreemde aan bovenstaand stukje code is dat de method setContentView wordt aangeroepen terwijl die nergens te bekennen is in de class: als je naar je eigen bestand MainActivity.java kijkt zie je namelijk nergens de method setContentView. Dit heeft te maken met hoe je class MainActivity gedefinieerd is in je bestand MainActivity.java. Of anders gezegd: dit hangt af van je beschrijving van je class block waar je de class MainActivity begint. Zoals je weet uit de vorige DevTutorial maak je een class block door een block (met een '{' en een '}') neer te zetten en daar vóór te beschrijven dat je een class block maakt door het woordje class te gebruiken. Dit kan je zien op regel 10: [gist id=1057256] Met class MainActivity zeg je: "de code in dit block is een class, en de class geef ik de naam MainActivity". Wat public betekent is nu nog niet belangrijk. Maar waar we het nu over gaan hebben is wat daarachter staat, namelijk extends Activity. Waarbij vooral het woord extends erg belangrijk is. Wanneer je het woord extends gebruikt in Android dan betekent dat je een een uitbreiding maakt op de class die je daarna noemt, in dit geval Activity (extends is Engels voor 'uitbreiden' of eigenlijk 'breidt uit'). En alles wat je verder in je huidige class (in dit geval MainActivity) schrijft, wordt gemengd met de oorspronkelijke class: je neemt een andere class als basis en daar werk je mee verder en dat wordt je eigen class inclusief de functionaliteit (hoofdzakelijk methods) van de class die je uitbreidt. De class die je als basis neemt heet de superclass, de class waar je mee verder werkt heet de subclass. In dit voorbeeld is Activity de superclass en MainActivity de subclass. Dus wanneer je een class subclasst door het woord extends te gebruiken, dan werk je eigenlijk in een al bestaande class en voeg je alleen maar dingen toe. Maar hoe weet jij nou wat het woordje Activity achter extends betekent, en nog belangrijker: hoe weet Android nou wat Activity betekent? Dit werkt op dezelfde manier als wat je in de vorige DevTutorial deed met View, Button, EditText of Toast. Op regel 3 van MainActivity.java staat: [gist id=1210972] Hiermee vertel je aan Android dat je met de Activity class wil werken en dat Android hem kan vinden in de package android.app. Wanneer Android je code inleest om die te gebruiken en hij komt op regel 10 dan denkt hij ongeveer dit:

"ah, de programmeur wil een nieuwe class aanmaken, met de naam MainActivity. Oh wacht even, ik zie aan het woordje extends dat het een uitbreiding is op de class Activity. Wat is Activity ook alweer? Oh ja, dat heeft de programmeur aan het begin van het bestand beschreven bij zijn import statements: ik heb op regel 3 gelezen dat de class Activity in de package android.app zit."

Vervolgens kopieert Android de code van de class Activity uit de package android.app bij je code van je eigen class MainActivity. De methods die gekopieerd worden kan je hier zien: http://developer.android.com/reference/android/app/Activity.html. Als je iets naar beneden scrollt naar het kopje public methods zie je alle methods die aanwezig zijn in de Activity class: . Dit betekent dat al deze methods door jou gebruikt kunnen worden omdat Activity de superclass is van je subclass MainActivity (daaronder staan de protected methods, waarom je deze ook mag gebruiken kom ik in een latere DevTutorial op terug). Doordat je weet welke methods de Activity class heeft, kan je afleiden wat je ermee kan doen en dus ook hoe de Activity class zich gedraagt. Hierdoor zou je de beschrijving van alle methods van een class samen ook het DNA van een class kunnen noemen. Als je iets naar beneden scrollt zie je de method findViewById staan: (en als je op findViewById klikt, kom je op de uitgebreide method-beschrijving: ). Deze heb je in de vorige DevTutorial gebruikt om een Button en een EditText op te zoeken. Toen je deze code schreef: [gist id=1210990] Maakte je dus gebruik van de method findViewById die je op die webpagina van de Activity class ziet staan. Scroll je nog iets verder naar beneden, dan zie je de method "setContentView" staan: (en als je op setContentView klikt kom je op de uitgebreide method beschrijving: ). Deze method gebruikte je dus op regel 19: [gist id=1211001] En toen maakte je dus gebruik van de method setContentView die je op de webpagina van de Activity class ziet staan. Op dezelfde manier zijn er veel meer methods die je al kan gebruiken in je class MainActivity omdat je hem subclasst op basis van de Activity superclass. Kijk eens door de lijst van methods zodat je een beetje door hebt dat er al erg veel functionaliteit voor je beschikbaar is die kan gebruiken voor je eigen Activities. Kijk bijvoorbeeld ook even naar de method getLocalClassName en probeer te begrijpen wat die doet. Bij het subclassen van de Activity class geef je je eigen class al een hele duidelijke basis: hij beschikt over meerdere methods (zoals setContentView, findViewById en onCreate) die je dwingen je eigen class op een bepaalde manier verder op te bouwen. Maar aan de andere kant is dat ook erg handig, probeer maar eens zelf een scherm op te bouwen zonder gebruik te maken van de methods die je in de Activity class vindt:

[gist id=1210994] Daar sta je dan met je eigen mini-class..... er staan overal foutmeldingen, simpelweg omdat je niet meer gebruik kan maken van de methods die in de Activity class zitten. Ik heb er even een screenshot van gemaakt waar je in de onderste helft van het scherm ook de beschrijvingen van de foutmeldingen ziet staan: . Er staat een fout op regel 20 en 21 omdat je niet meer de method findViewById kan gebruiken (of je moet hem nu zelf maken) met als foutmelding: 'The method findViewById(int) is undefined for the type MainActivity' dat in gewoon Nederlands betekent 'ik zie nergens een method findViewById staan, dan mag je die method ook niet aanroepen'. Eenzelfde soort fout is ook te zien op regel 19 omdat de method setContentView niet meer aanwezig is. De andere foutmeldingen hebben ook te maken met ontbrekende methods. Behalve regel 28, dat is een iets andere soort foutmelding, daar kom ik een andere DevTutorial nog op terug.

Ik schreef net dat door de Activity class te subclassen je gebruik kan maken van onder andere de method onCreate. Deze method kan je ook zien op de webpagina van de Activity class die ik je net gaf. Maar het rare is dat wanneer je naar je eigen MainActivity class kijkt je óók een method onCreate ziet staan. Wat er hier eigenlijk gebeurt is het overschrijven van een method, hiervoor moet je een regel boven je method de tekst @Override zetten (zie regel 16). Hierdoor zeg je tegen Android dat je de method wil overschijven en als gevolg hiervan controleert Android of de method die je wil overschrijven wel aanwezig is in de superclass. De tekst @Override is niet verplicht maar is wel handig om te doen, dan weet je beter wat je code doet, en je krijgt een foutmelding als er geen zelfde method in de superclass aanwezig is: dit voorkomt fouten. Wanneer je een method overschrijft, kan je deze alsnog aanroepen. Dat doe je met het woordje super wat je kan zien op regel 18. Bij sommige methods is het verplicht om de super method aan te roepen (zoals bij onCreate, onPause, onDestroy en onResume). Het handige aan methods overschrijven is dat je weet wanneer de methods worden uitgevoerd, hierdoor kan je code schrijven in specifieke methods zodat je weet dat je code op vaste momenten wordt uitgevoerd (zoals je al gedaan hebt met onCreate in de vorige DevTutorial en je nog vaak zult doen).

Nu we aan het einde zijn van de uitleg over extends hoop ik dat je het volgende blijft onthouden: door een class te subclassen bouw je voort op die class (superclass) en kan je zo de methods gebruiken van de superclass in je subclass die je zelf verder schrijft. Om zelf een Activity te maken moet je de Activity Class subclassen, je eerste Activity kan je automatisch aanmaken bij een nieuw Androidproject door een vinkje te zetten bij Create Activity. Door de Activity class te subclassen krijg je onder andere de belangrijkste layout-gerelateerde methods setContentView en findViewById.

Layouts, R.java, setContentView en findViewById

Nu wordt beschreven hoe de methods setContentView en findViewById via R.java inhaken op de layouts die je maakt voor je app. Kijk nog eens naar de uitgebreide beschrijvingen van de methods findViewById en setContentView: Je ziet achter de naam van de method tussen haakjes de volgende teksten staan: int id en int layoutResID. Dat betekent dat wanneer je deze methods aanroept je een bericht mee moet geven dat beschrijft welke View je wilt opzoeken of welke layout je wilt gebruiken. Het woord int betekent dat het bericht een afgerond getal moet zijn (zoals 9342802 of 123022), en id en layoutResId moet je zien als een uitleg die beschrijft wat het nummer betekent: de id (identificatie) van de View die je wil opzoeken of de layout die je wilt laden. Views en layouts zijn beiden resources, dat zijn externe bestanden (of delen daarvan) die in je app aangeroepen worden door je code. Bij layouts zijn deze resources op zichzelf staande layout xml-bestanden en bij Views zijn de resources delen van deze xml-bestanden. Zoals je misschien al weet staan deze resources in de map res die je ziet staan in het Package Explorer Window. De id (dat afgeronde getal) dat je als bericht moet meegeven bij de methods is een unieke verwijzing naar de betreffende layout of View. Die id (dat afgeronde unieke getal) hoef je niet zelf te onthouden maar kun je krijgen door R.id.id_van_view of R.layout.naam_van_layout te typen. Let er dan wel op dat je id_van_view en naam_van_layout respectievelijk moet vervangen door de id van de View en de bestandsnaam van de layout die je wil gebruiken (zoals bijvoorbeeld R.id.etxtAmount verwijst naar de EditText uit main.xml of R.layout.main verwijst naar het layout-bestand main.xml). Wanneer je R.id.id_van_view gebruikt, dan maak je eigenlijk een verwijzing naar het getal id_van_view (dus bijvoorbeeld etxtAmount) die in de class id staat die op zijn beurt in de class R staat. En wanneer je R.layout.naam_van_layout gebruikt, dan maak je eigenlijk een verwijzing naar het getal naam_van_layout (dus bijvoorbeeld main) die in de class layout staat die op zijn beurt in de class R staat. Deze class R kan je vinden in het Package explorer window in het bestand R.java in de map gen. Als je een layout bestand hebt aangepast en hebt opgeslagen zonder foutmeldingen, dan ververst Eclipse automatisch het bestand R.java. Pas wanneer je layout of View daadwerkelijk een getal toegewezen heeft gekregen in in R.java, dan kan je hem gebruiken in je java class. Als er een fout zit in een layout-bestand dan wordt R.java niet automatisch ververst omdat er een fout optreedt waardoor het automatisch genereren wordt afgebroken. Dus stel je bent in een main.xml bezig maar in een ander layout bestand zit nog een fout, dan moet je die fout eerst oplossen voordat je R.layout.main kan gebruiken in je Activity. Dit heeft te maken met hoe Eclipse het bestand R.java genereert. Weet je nog dat je de namen die je gaf aan je Button en EditText moest laten beginnen met @+id/? Het apenstaartje is een signaal voor Eclipse dat hij een verwijzing tegenkomt die te maken heeft met een nummer (in het geval van een Button is dat een id). Het plusje is vervolgens een signaal dat Eclipse hier een nummer voor moet genereren en "id/" betekent dat het nummer geplaatst moet worden in het class block genaamd "id" in R.java. De naam die daarna komt is de naam die het nummer gegeven wordt in het code block. Dit staat ook uitgelegd in de volgende screenshot: . In deze screenshot staat in het Package Explorer Window een paars rechthoek om de map layout. Alle layout-bestanden die je ooit zult maken komen automatisch terecht in het paarse rechthoek in het bestand R.java. Op dezelfde manier worden alle Views die je ooit aanmaakt in alle layout-bestanden automatisch gegenereerd in de class id, de class die boven layout staat. Een belangrijke eigenschap van het bestand R.java is dat het automatisch gegenereerd wordt. Om deze reden mag je R.java zelf niet aanpassen; dat moet je automatisch door Eclipse laten doen. Je moet dit niet forceren of zelf dingen gaan bijschrijven in R.java: het enige dat je kan doen is .xml bestanden schrijven zonder fouten erin. Als je in de menubalk bovenin Eclipse bij Project geen vinkje hebt staan bij Build Automatically dan wordt je R.java alleen ververst als je je app opstart of als je op Project --> Build Project klikt. Doordat R.java automatisch wordt bijgehouden door Eclipse kan je in je java code op een makkelijke manier verwijzen naar de views, layouts en andere dingen die je beschrijft in de de map res: R.java is de verbindingsbrug tussen de bestanden die je in de map res maakt/aanpast en de java code die je schrijft in de map src (alle andere soorten bestanden in de map res hebben ook hun eigen class in de class R, hier leer je over in een volgende tutorial).

Als je goed kijkt zie je dat R.java eigenlijk een class is: op regel 10 begint het block van de class met de naam R. Binnenin het block van de class R staan zogenaamde inner classes beschreven: een class die weer staat in een andere class. Deze inner classes zijn atrr, drawable, id, layout en string. Op regel 8 zie je staan bij welke package (groep van classes) deze class hoort. Als je die naam vergelijkt met het begin van MainActivity.java zie je dat ze bij dezelfde package horen. Was dit niet het geval geweest, dan had je nog het woord import moeten gebruiken als je in MainActivity wilde verwijzen naar de layouts en Id's (voor classes die in dezelfde package zitten hoef je geen imports te gebruiken, weet je nog?).

Wanneer je findViewById of setContentView gebruikt dan geef je dus eigenlijk een getal mee die staat in het bestand R.java. R.java is gegenereerd door de ADT van Eclipse en Android gebruikt de nummers uit R.java om tijdens het draaien deze te koppelen aan de layouts en de Views. Wanneer je dus dit schrijft:

[gist id=1211001] Zeg je eigenlijk in gewoon Nederlands: "zorg ervoor dat het scherm er anders uit komt te zien: ga de layout veranderen. En ik heb er nog extra bericht bij: je moet dit doen met de layout waarvan het nummer met de naam main staat in de class layout die in de class R staat." Wanneer je dit schrijft: [gist id=1211673] Zeg je eigenlijk in gewoon Nederlands: "van de layout die je nu geladen hebt in het scherm, ga op zoek naar een View die als nummer de naam etxtAmount heeft. Dit nummer kan je vinden in de class id die in de class R zit." Als gevolg daarvan gaat Android op zoek naar de View met dat nummer en geeft je een View terug.

The class hierarchy (hiërarchie van classes)

Het begin van deze DevTutorial beschreef dat alle knoppen, velden, leegruimtes en andere elementen die je op scherm ziet, Views zijn. Bij een EditText kan je dat zien door naar de beschrijving ervan te gaan. Je ziet daar bovenin staan extends Textview , dat betekent dat de EditText een subclass is van TextView. Dus alle methods die aanwezig zijn in een TextView zijn ook aanwezig in een EditText: hierdoor is elke EditText die je ooit gebruikt voor Android ook een TextView, maar niet elke TextView is een EditText (net zoals dat alle koeien dieren zijn, maar niet alle dieren zijn koeien). Kijk je naar de beschrijving van de TextView, dan zie je staan extends View , dat op dezelfde manier betekent dat elke elke TextView (en dus ook een EditText) een View is. Dit kan je ook andersom bekijken: als je op de beschrijving van de View class kijkt zie je bovenin staan Known Direct Subclasses en Known Indirect Subclasses: . Je ziet daar bij subclasses ook de class ViewGroup staan. Als je daarop door klikt, zie je de LinearLayout en de RelativeLayout beide een subclass zijn van ViewGroup: . Of andersom gezegd: de LinearLayout class en de RelativeLayout class zijn allebei een subclass van ViewGroup, en omdat ViewGroup een subclass is van de View class zijn de LinearLayout class en de RelativeLayout class ook allebei Views. Dit is wat ik bedoelde dat alles wat je ooit ziet in je Androidscherm een View is. Belangrijk puntje daarbij is dus dat er veel verschillende soorten Views zijn met hun eigen categorieën. Nu kan je deze regels code ook beter begrijpen: [gist id=1210990] Deze roepen zoals je weet deze method aan: Bij de beschrijving van de method staat in gewoon Nederlands: "Deze method zoekt een View in de layout xml-bestanden met het opgegeven unieke nummer". Wanneer je deze method gebruikt om een View op te halen (én de View is geladen in de layout die je hebt geladen met setContentView) dan krijg je een View terug. Het enige wat je dan nog moet doen is de View omvormen naar het juiste subclass-type. Op de bovenste regel van de regels code hierboven weet je dat een View terugkrijgt die ook nog als subclass type een EditText is: dat heb je immers zelf ingevuld in de Graphical Layout Editor Tab toen je je layout aan het ontwerpen was. Dit omvormen doe je door het class-type er tussen haakjes voor te zetten. Dit heb je al met de EditText gedaan hebt door er (EditText) voor te schrijven en dat vervolgens in de EditText mEtxtAmount te stoppen. In Java (en Android) noem je dit omvormen casten. Wanneer je in plaats van een EditText een Button ophaalt (zoals hierboven in de onderste regel code) dan moet je er (Button) voor zetten. Of ander voorbeeld: stel dat je de LinearLayout in je layout xml-bestand ook een id meegeeft dan kan je de LinearLayout ophalen met findViewById (een LinearLayout is uiteindelijk ook een subclass van de class View, weet je nog?). Daarbij kan je hem in een LinearLayout in je code te stoppen door de verkregen View met (LinearLayout) te casten (=om te vormen) naar een LinearLayout. Afhankelijk van wat voor type je een object toewijst kan je bepaalde methods wel of niet gebruiken. Stel dat je de EditText niet zou casten naar een EditText maar gewoon een View zou laten zijn door dit te typen: [gist id=1223739] dan kan je alleen nog maar de methods op het object mEtxtAmount gebruiken die horen bij de View class. Wil je later toch de methods van de EditText class gebruiken dan moet je hem alsnog casten: [gist id=1223742] Door op http://developer.android.com/reference/packages.html op de superclass of een van de subclasses te klikken van een class kan je alle classes van Android uitpluizen. De allerhoogste superclass van alle classes is de class Object. In DevTutorial 6 gaan we dieper in op de class hierarchy.

Gefeliciteerd! Je weet nu aardig goed hoe normale Views en ViewGroups en de layouts zelf in een layout xml-bestand geplaatst worden, deze vertaald worden naar een nummer in de R class en hoe je de methods setContentView en findViewById van de Activity class moet gebruiken om deze normale Views, ViewGroups en de layout te gebruiken. Ook weet je hoe het komt waarom je deze methods kan gebruiken in je eigen Activity subclass (bijvoorbeeld MainActivity) en hoe je de Views die je krijgt uit de method findViewById naar het juiste subclass-type van de View class kan casten. Deze Androidconcepten samen zijn een aardig op zichzelf staand stukje kennis. Er zijn een paar (ik schat 8-12) belangrijke Android-basisconcepten, waarvan je het "Layout-R-findViewById&setContentView"-concept nu vrij goed snapt.

De app ontwerpen: LinearLayout

Om met de LinearLayout te leren werken gaan we een afspraakplanner maken: een app waarop je een datum kan invoeren waarop je een afspraak wil plannen. Valt die geselecteerde datum op een zondag of een zaterdag, dan wordt de afspraak ingepland op de daarna komende maandag. De gebruiker kan kiezen of hij ook afspraken wil accepteren die op zaterdag vallen. Je leert ook te werken met de CheckBox en de DatePicker.

De LinearLayout is een typisch voorbeeld van een ViewGroup: met een LinearLayout reserveer je een rechthoekig vlak op het scherm. De Views die je dan weer in je LinearLayout plaatst worden getoond in het rechthoekig vlak. De Views die je in de LinearLayout stopt worden in één lijn getoond: deze lijn kan je door middel van de property Orientation horizontal of vertical instellen (dus alle Views worden onder elkaar óf naast elkaar getoond). Het aanmaken van een nieuwe LinearLayout kan je doen met de Android xml file creator: een scherm waarin je in kan aangeven wat voor type bestand je wil hebben. Als je kiest voor het type Layout kan je vervolgens aangeven dat een Layout van het type LinearLayout wilt maken. Dat betekent dat de ViewGroup die alle andere Views in je layout bevat een LinearLayout is. De ViewGroup die alle andere Views bevat noem je ook wel het Root element (root is het engels voor wortel, en betekent in dit geval de basis/oorsprong van de rest van de layout). Zodra je een nieuwe LinearLayout aanmaakt verschijnt hij in het bestand R.java en kan je hem gebruiken in je Activity met de method setContentView. Wanneer je je layout aan gaat passen in de Graphical layout editor tab kan je heel gemakkelijk de Views die in je layout wilt hebben slepen in het Outline window: dan worden de View automatisch achter of onder elkaar neer gezet, afhankelijk van de Orientation property. We gaan nu een LinearLayout aanmaken, de oriëntatie aanpassen, views aanpassen en hem laden in de class MainActivity.

[gist id=1062276] Je ziet dat de foutmelding is verdwenen. Omdat je net de layout linearlayout hebt aangemaakt, is het nummer linearlayout aangemaakt in de subclass layout van de class R. Dus wanneer je een verwijzing maakt naar de class R, en daarvan de inner class layout, en daarvan het nummer linearlayout, dan weet Android nu weer wat je bedoelt

ElementBeschrijving
Layout height De hoogte van de Button. Je moet dit invullen met dip door bijvoorbeeld 60dip in te vullen, maar je kan er ook voor kiezen dat de Button zo groot wordt als hij zelf vindt dat nodig is door er wrap_content in te vullen. Een andere optie is om de Button zo groot mogelijk te maken, dit doe je met fill_parent.
Layout width Zelfde als Layout height, maar dan in de breedte.
Layout margin hoeveel extra ruimte moet er geplaatst worden tussen de Button en andere widgets die op het scherm zitten. Je moet dit invullen in dip, een voorbeeld is om in te vullen: 20dip.

dip

dip is een afkorting voor de Engelse term "Density-independent Pixels", dat in gewoon Nederlands betekent: een maat voor het meten van de afstand op het scherm gebasseerd op de fysieke grootte. Op een normaal scherm (bijvoorbeeld het scherm van de HTC legend of de HTC Hero is 1dip gelijk aan 1 pixel. Maar op een scherm waar er meer pixels per vierkante centimeter zijn (bijvoorbeeld het scherm van de Samsung Galaxy S), dan is 1 dip gelijk aan 1,5 pixel. Wanneer je alle afstanden en groottes beschrijft in dip heb je in de meeste gevallen een redelijk aardige maatstaf om je scherm op te bouwen, onafhankelijk van het toestel waarvoor je ontwikkelt.
ElementPropertyWaarde
Button Text Plan mij in
Button On click planMe
Button Id @+id/btnPlanMe
CheckBox Id @+id/cbSaturdaysAllowed
CheckBox Text Zaterdagen toegestaan
CheckBox Layout margin left 20dip
DatePicker Id @+id/dpDate
DatePicker Layout margin left 20dip
DatePicker Layout margin top 20dip

De app programmeren: CheckBox en DatePicker

De app moet geprogrammeerd worden zodat hij de geselecteerde dag als antwoord teruggeeft. Selecteer je per ongeluk een datum die op een zaterdag of een zondag valt, dan moet de app automatisch als antwoord geven de maandag die daarop volgt. Wil je ook op zaterdagen werken, dan moet de app daar ook rekening mee houden. Dit moet net zoals in de eerste tutorial geprogrammeerd worden in MainActivity.java.

De komende punten gaan over het definiëren van Views en de onClick method van de button aanmaken. Her en der worden je nog tips gegeven hoe je dit aan moet pakken, maar er wordt eigenlijk vanuit gegaan dat je dit al vanuit jezelf kan doen zoals je leerde aan het einde van de vorige DevTutorial (lees dat anders nog een keer opnieuw).

Views definiëren en onClick method maken

[gist id=1062368]

[gist id=1062369]

DatePicker: Datum uitlezen en bewerken

In de method planMe willen we de berekening uitvoeren die deze app moet doen. We gaan eerst de datum opvragen die geselecteerd is. Deze datum willen we ergens kwijt. Om een datum op te slaan kan het Calendar object gebruikt worden. Dit object komt van een class die methods heeft om de datum te onthouden en vervolgens je kan vertellen op welke dag van de week die datum valt.

[gist id=1062451] Eerst wordt een Calendar aangemaakt, daarna worden methods aangeroepen van de DatePicker om geselecteerde dag, maand en jaar op te vragen. Daarna wordt de method set aangeroepen. Dit is een method die drie berichten nodig heeft en op basis daarvan zichzelf instelt op een bepaalde datum (Vergeet niet de class java.util.Calendar te importeren).

[gist id=1062456] Zoals eerder verteld kan je met een if-code block een vraag stellen die je binnen de haakjes opschrijft. Wordt die vraag met ja beantwoord, dan wordt het code block dat je daarna beschrijft uitgevoerd. Aan de linkerkant van de == wordt de method get opgeroepen op je Calendar object cal. Deze method kan het jaartal (Calendar.YEAR), nummer van de maand (Calendar.DAY_OF_MONTH) of een andere waarde teruggegeven aan de hand van welk bericht je mee stuurt. In dit geval word dagnummer van de week opgevraagd. Aan de rechterkant van de == wordt een naam van een getal gebruikt. In dit geval wordt de naam SUNDAY gebruikt, dat staat voor het getal 1: de eerste dag van de week. In gewone taal wordt dus de volgende vraag gesteld: "wat is het dagnummer van de week die geselecteerd is, is dat het dagnummer 1 dat ik beschrijf door de naam Calendar.SUNDAY te gebruiken?". Wanneer die vraag met ja beantwoord wordt moet je de datum die je in cal hebt opgeslagen met een dag ophogen.

[gist id=1062458] Hiermee geef je aan dat je dag van de maand (DAY_OF_MONTH) wil ophogen en dat je dat 1 keer wilt doen. Hetzelfde gaan we doen met wanneer de geselecteerde datum een zaterdag is. Het verschil is dat we twee vragen willen stellen voordat we de dag van de maand met twee dagen willen ophogen. De extra vraag die we willen stellen is: staat er géén vinkje bij de CheckBox mCbSaturdaysAllowed? Om dit doen moeten we de method isChecked gebruiken van de CheckBox. Maar hiermee komen we er niet: want als er juist een vinkje staat dan pas wordt de vraag beantwoord met ja. We willen juist dat wanneer er geen vinkje staat bij de CheckBox de vraag wordt beantwoord met ja. Het antwoord dat je krijgt wanneer je met een if-code block werkt kan je omdraaien met een '!'. Dus door het volgende te schrijven stel je de vraag "staat er géén vinkje bij de CheckBox": [gist id=1062459] Zoals je weet moeten we twee vragen stellen voordat het if block wordt uitgevoerd. Je kan twee vragen stellen door && te gebruiken.

[gist id= 1062464] Hiermee vraag je dus: "staat er geen vinkje bij de checkbox én is de geselecteerde dag een zaterdag? Wanneer deze beide vragen met ja worden beantwoord moet de geselecteerde datum met 2 dagen ophogen.

[gist id=1062466] Wanneer Android de bovenstaande code heeft uitgevoerd heeft hij de datum waarop je afspraak ingepland moet worden. Die datum is ingesteld in het Calendar object cal. Die datum staat er in als een verzameling van getallen, deze kan je niet zomaar uitlezen om een mooie datum notatie te krijgen. Hiervoor gebruik je de volgende code;

[gist id=1063180] Bovenstaande regels zorgen ervoor dat de datum komt te staan in de String genaamd date. Een String is een object dat een woord of een zin of een combinatie van letters kan onthouden zodat je deze op het scherm kan laten zien of kan gebruiken om een zin mee te bouwen. Hoe deze twee bovenstaande regels verder werken is te uitgebreid voor deze tutorial en wordt dus niet besproken. Als je wil weten wat de combinatie "dd-MM-YYYY" betekent raad ik je aan te kijken naar de beschrijving van de class SimpleDateFormat (en kijk op die pagina naar de tabel "Symbol - Meaning - Presentation - Example").

[gist id=1063181]

[gist id=1063182] (en vergeet uiteraard niet de class android.widget.Toast te importeren bovenaan de class MainActivity) De class moet er nu als volgt uit zien: [gist id=1063187]

De app ontwerpen: RelativeLayout

Je hebt net een mooie simpele app gemaakt. Je mag jezelf daar best een complimentje voor geven. Er doet zich echter een klein probleempje voor: wanneer je je telefoon op zijn kant draait, dan klopt de indeling (layout) van het scherm niet meer. Wanneer je de app hebt opgestart in je emulator kan je het scherm van je telefoon draaien door op Ctrl+F11 te drukken (krijg je een zwart scherm, druk dan enkele keren op Ctrl+F11 of Ctrl+F12). Je ziet nu dat de minnetjes van de DatePicker niet meer worden getoond: . Je loopt nu tegen een probleem aan van de LinearLayout: Views kan je alleen maar in één lijn onder elkaar óf naast elkaar tonen. Een layout die dit probleem niet heeft is de RelativeLayout. Wanneer je een RelativeLayout gebruikt, krijgen de Views die in de RelativeLayout zitten extra properties. Je kan dan per View instellen of je hem links, rechts, onder of boven wilt uitlijnen, of dat je hem links, rechts, onder of boven een andere View wil plaatsen. Ook kan je dan instellen per View, of je de grootte aan wilt passen aan een andere View. Doordat je deze properties aan kan passen, kan je Views op heel veel manieren neerzetten in je RelativeLayout. Hierdoor kan je je layout veel beter voorbereiden op lastige situaties waarin je nog steeds wilt dat je layout er netjes uitziet. Als je een LinearLayout om wilt zetten naar een RelativeLayout, hoef je niet alle Views opnieuw in je layout te slepen in de Graphical Layout editor tab. Wat je veel beter kan doen, is met de Android XML Creator een nieuwe layout aanmaken en als Root element de Relative Layout kiezen. Daarna kan je in de Sourcecode editor tab (dus de meest rechtse editor tab) van het LinearLayout-bestand alle Views die in de LinearLayout zitten kopiëren in je RelativeLayout. Wanneer je twee layouts in één Activity wilt gebruiken, kan je hiervoor resource qualifiers gebruiken. Resource qualifiers zijn woordjes die je toe moet voegen aan de naam van de map layout, zodat je een extra map krijgt waarin alternatieve bestanden zitten voor je layouts. Wanneer je bijvoorbeeld de resource qualifier land gebruikt, maak je een nieuwe map layout-land aan in je res map in het Package Explorer window. Als je app vervolgens in landschap-modus is, kijkt hij bij het laden van layouts eerst in de map layout-land en als hij daar niet het benodigde bestand kan vinden kijkt hij pas in de map layout. Op dezelfde manier heb je onder andere ook qualifiers voor portret-modus, de taal-instellingen waarmee de app wordt opgestart of de schermgrootte van het toestel. Aan het einde van deze tutorial moet het je duidelijk zijn hoe je de extra propertjes in de RelativeLayout kan gebruiken om je Views dynamischer neer te zetten in je layouts. Dit ga je ontdekken door een layout aan te maken voor wanneer het scherm gekanteld is en daarvoor de RelativeLayout te gebruiken:

[gist id=1063254]

Er is er een nieuw layout-bestand aangemaakt in de map layout-land. Wanneer Android een layout moet laden en het scherm is in landschap-modus, dan kijkt hij eerst in de map layout-land. Vindt hij daar geen layout met dezelfde naam, dan gebruikt hij de layout die hij vindt in de map layout.

Zoals je kon zien op het vorige scherm, kan je op dezelfde manier ook andere qualifiers gebruiken of ze zelfs combineren. Je kan bijvoorbeeld een layout aanmaken en de qualifiers Orientation en Language toevoegen en deze respectievelijk op portret en nl zetten. Op deze manier wordt de layout alleen geladen als: (1) je in portret-modus bent én (2) het wordt gebruikt op een toestel die als landinstelling Nederland heeft geselecteerd.

[gist id=1064803]

Vergelijk nu eens de properties van de DatePicker in je oude LinearLayout () met de properties van de DatePicker in je nieuwe RelativeLayout(). Als je bij beide in het Properties window bij Misc kijkt, dan zie je bij de RelativeLayout dat je bij de DatePicker meer properties kan instellen bij Misc: Je kan daar bijvoorbeeld Layout to left of of Layout align parent right instellen. Wanneer je een View (bijvoorbeeld een DatePicker) gebruikt in een RelativeLayout, kan je meer properties instellen in de View, zodat je hem preciezer kan positioneren. Wanneer je een View in een RelativeLayout zet krijgt de View onder andere de volgende eigenschappen (properties):

ElementBeschrijving
Layout above Positioneert de View boven de View die je hier invult.
Layout align baseline Positioneert de View op dezelfde hoogte als de View die je hier invult, gebaseerd op de baseline van beide Views. De baseline is een denkbeeldige horizontale lijn die over een View loopt en gebruikt wordt om Views voor het oog netjes op "dezelfde" hoogte te positioneren.
Layout align left Positioneert de linkerrand van de View aan de dezelfde horizontale afstand van de linkerrand van de View die je hier invult.
Layout align parent right Positioneert de rechterrand van de View aan de rechterrand van de RelativeLayout.
Layout to left of Positioneert de rechterrand van de View aan de linkerrand van de View die je hier invult.
ElementPropertyWaarde
DatePicker Layout below @+id/btnPlanMe
DatePicker Layout align right @+id/btnPlanMe
DatePicker Layout margin left  
DatePicker Layout margin top  
CheckBox Layout align left @+id/btnPlanMe
CheckBox Layout below @+id/btnPlanMe
CheckBox Layout margin left  

Als gevolg hiervan wordt bij een klein scherm de rechterrand van de CheckBox op dezelfde horizontale afstand gezet als de linkerrand van de DatePicker: de CheckBox wordt ingekort en daardoor worden de woorden "Zaterdagen" en "toegestaan" onder elkaar geplaatst: .

Toen je de properties van de DatePicker en de CheckBox aanpaste (Layout below, Layout align right etcetera) en daar steeds @+id/btnPlanMe invulde, heb je ingesteld dat de Widgets relatief van de Button moesten worden weergegeven. De DatePicker moet rechts uitgelijnd worden ten opzichte van de Button en moet onder de Button gepositioneerd worden. Op dezelfde manier moet de CheckBox links uitgelijnd worden ten opzichte van de Button en ook onder de Button gepositioneerd worden. Door van de CheckBox de property Layout to left of op @+id/dpDate te zetten, heb je ervoor gezorgd dat de CheckBox niet achter de DatePicker verdwijnt en als gevolg daarvan de tekst altijd zichtbaar is.

Dit is het einde van deze DevTutorial. Hopelijk vond je het leuk om deze te lezen en ik zie je graag terug op het forum of in de volgende DevTutorial! Volgende week leg ik je uit wat een ScrollView is, wat parameters en variables zijn en wat de logcat is. Ook leer je daar hoe je een for block en een try-catch block maakt en hoe je door je eigen code kan lopen met breakpoints.

Lees meer over:
DevTutorial

Plaats reactie

666

0 reacties

Laad meer reacties Bekijk alle reacties

Meest gelezen

3 manieren om stiekem en ongemerkt WhatsApp-berichten te lezen

Berichten-apps komen met de handige bevestiging om te zien of de ander jouw berichtje heeft gelezen. Toch kan je in apps als...

Google Pixel 5a (5G) komt definitief niet naar Europa

Vrijdagavond deden geruchten de ronde over een afgestelde lancering van de Pixel 5a. Mede door de chiptekorten zou Google de...

Gegevens van 500 miljoen LinkedIn-gebruikers online te koop aangeboden

Na een immens datalek bij Facebook, lijkt het ditmaal mis te zijn gegaan bij LinkedIn. Via een bekend hackersforum wordt name...

Google bevestigt einde Shopping-app, gaat door als website

Googles Graveyard, gevuld met apps die na jaren in dienst te hebben gestaan van gebruikers wereldwijd worden afgedankt, wordt...

Fitbit wil bloeddrukmetingen mogelijk maken op zijn smartwatches

Bloeddrukmetingen zijn normaliter alleen weggelegd voor dure apparatuur die men gebruikt in ziekenhuizen of huisartsenposten....

Lees meer

Net binnen

Fitbit wil bloeddrukmetingen mogelijk maken op zijn smartwatches

Bloeddrukmetingen zijn normaliter alleen weggelegd voor dure apparatuur die men gebruikt in ziekenhuizen of huisartsenposten....

Gegevens van 500 miljoen LinkedIn-gebruikers online te koop aangeboden

Na een immens datalek bij Facebook, lijkt het ditmaal mis te zijn gegaan bij LinkedIn. Via een bekend hackersforum wordt name...

Google bevestigt einde Shopping-app, gaat door als website

Googles Graveyard, gevuld met apps die na jaren in dienst te hebben gestaan van gebruikers wereldwijd worden afgedankt, wordt...

3 manieren om stiekem en ongemerkt WhatsApp-berichten te lezen

Berichten-apps komen met de handige bevestiging om te zien of de ander jouw berichtje heeft gelezen. Toch kan je in apps als...

Google Pixel 5a (5G) komt definitief niet naar Europa

Vrijdagavond deden geruchten de ronde over een afgestelde lancering van de Pixel 5a. Mede door de chiptekorten zou Google de...

Lees meer