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 ! 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.
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: https://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.
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).
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 https://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.
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
Element | Beschrijving |
---|---|
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. |
Element | Property | Waarde |
---|---|---|
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.
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.
[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):
Element | Beschrijving |
---|---|
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. |
Element | Property | Waarde |
---|---|---|
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: .
Dit is het einde van deze DevTutorial. Hopelijk vond je het leuk om deze te lezen en ik zie je graag terug op het 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.
Reacties
Inloggen of registreren
om een reactie achter te laten
Helaas niet geheel origineel maar Wouter ………………. helemaal geweldig deze tuts !!! Ik had een dik boek over Android en Apps om het onder de knie te krijgen maar dit boek heb ik inmiddels naast mij gelegd en ben mij door deze 10 tuts aan het ‘worstelen’ ……….. 😉
Nu ben ik helaas net begonnen terwijl een paar dagen geleden de site herstijld is (mooi werk trouwens) maar dit houdt wel in dat (voorlopig) veel afbeeldingen niet meer te vinden zijn en er dus zwarte balken op de pagina staan (http://images.androidworld.nl/wp-content/uploads/Screen-shot-2011-07-03-at-16.09.28.png)
Enig idee wanneer dit weer verholpen is zodat ik na de theorie ook kan gaan beginnen met tut numero 3?
Keep up the good work !!
Na weken me suf gelezen te hebben gaf dit me het juiste beeld van de java structuur
Hi Wouter,
Naast een formidabel programmeur heb je ook nog een gave om iets duidelijk over te brengen. Waanzinnig dat je hier al die tijd en moeite in hebt gestopt!
Ik vraag me werkelijk af hoe je zelf al die kennis hebt opgedaan.
Keep up the good work!Thanks!
Helemaal geweldig. Er zal heel wat werk in deze tuts zitten maar wat ben ik er blij mee. Op naar aflevering 4!!!
Waar staat de Donatie Bar?
Top werk
Ik vind deze tutorials echt geniaal!
Ik wilde leren hoe je apps kan maken, maar ik snapte er helemaal niks van.
Maar nu snap ik het, alleen ben ik nu pas bij deel 3. maar echt, dit is echt geniaal.
bedankt
Ik vind deze tutorials echt geniaal!
ik wilde leren hoe je apps kan maken, maar ik snapte er helemaal niks van!
maar nu snap ik het, alleen ben ik nu pas bij deel 3. maar echt, dit is echt geniaal\
bedankt
Mooie initiatief en super leerzaam, bedankt!
Ik, studente economie die niets af weet van programmering, heb dankzij de eerste drie tutorials toch twee (simpele) applicaties kunnen maken, unbelievable :-).
In een woord: TOP !
Ik heb al wat lopen snuffelen maar dan is én android leren en niet natuve engels spreken best wel een moeilijk combi. Aan de hand van je tut zie ik mezelf (in vergelijk met de vorige pogingen) reuze stappen maken. Een pdf/ebook zou het helemaal af maken !
Oke ik heb nog wat tuts te gaan maar hopelijk ga/blijf je er nog meer maken 😀
Zeer goede tutorials!!! Proficiat hiervoor!!!
Had ll. nog een engelstalig boek aangeschaft om te leren programmeren in Android, maar dat was net iets te hoog gegrepen daar ik ook maar beginner ben.
Jou tutorials worden veel beter begrepen voor beginners en zo haak je ook minder snel af.
Intussen heb ik tot aan deze tutorial alles perfect kunnen uitvoeren.
Heb me wel een tweedehandsboek ‘Java’ aangeschaft als referentie.
Hopelijk komt er binnenkort een pdf’je uit van alle tutorials.
Weer een leerzame tutorial. Super! Morgen misschien door met nummer 4. Bedankt.
Deze tutorial is zo top! Bedankt, Wouter!
Heb de tutorial afgerond. Het is helemaal gelukt. Nu op naar nummer 4 🙂
Wouter, top man!
Ik heb al eerder een poging gedaan tot het leren van java (specifiek android) maar daar ben ik afgehaakt door tijd probleempjes.
Deze DevTut’s zijn DE manier om goed en duidelijk te leren hoe je met het hele programeren om gaat, en daar dank ik je voor!
Zelf al weer lekker bezig met futsellen aan de apps, dus gaat allemaal helemaal goedkomen!
Ik ga er morgen ook maar eens aan beginnen. Goed werk!
Opzich leuke tutorials, maar ik vind dat je als beginner met Android eerst maar eens met Java moet gaan stoeien. Zo zijn die Calendar classen helemaal niet Android specifiek. Ik raad mensen zonder programmeer/Java ervaring dit boek aan. http://portal.aauj.edu/e_books/teach_your_self_java_in_21_days.pdf
Daarna zou ik pas aan Android beginnen en daar de tutorials lezen.
Hoi Wouter,
Super deze tutorials! Ik heb grote waardering voor het feit dát je dit doet, en voor de manier waarop je in staat bent om alles uit te leggen. Ik weet uit ervaring hoe moeilijk het is o.a. om je ‘blanco lezers’ steeds een stapje voor te zijn in wat ze allemaal verkeerd kunnen interpreteren 🙂
Tot nu toe is alles gelukt, ik ben helemaal trots. En zoals veel anderen zeggen: ik kan niet wachten op de volgende (hoewel deze drie me vannacht al aardig wat nachtrust gekost hebben 🙂
Hey wouter,
Bedankt voor de mooie en duidelijke tutorial, maar Ik ben de mist in gegaan met de vorige tutorial (tutorial 2).
Is het mogelijk en misschien wel een goed idee dat je de eindversie van ieder tutorial online zet, voor als het bij sommigen niet helemaal lukt (Maar een ideetje hoor!).
M.v.g.
Wederom complimenten voor de heldere uitleg. Ik kijk alweer uit naar de volgende!
Wauw, ongelofelijk bedankt. Je zal erg veel tijd hier aan kwijt zijn om het goed uit te leggen. Ga zo door!
Prima te doen, thnxs!
wel klein foutje, id van button is @+id/PlanMe
terwijl je deze later aanroept als findViewById(R.id.btnPlanMe);
Volgens mij is de grootste truc is nog beetje het objectmodel snappen… kan ff duren
Hi Wouter,
Weer een erg helder geschreven tutorial! Kan niet wachten op de volgende.
Zouden ze ook als pdf kunnen komen.
Door andere projecten heb ik hier nu te weinig tijd voor, maar ik wil dit zeker gaan doen. Het is nu heel lastig omdat de plaatjes ook verkleind zijn.
Bedankt weer voor de tutorial! Op dit moment heb ik geen tijd om ze allemaal te volgen, maar als ik weer tijd krijg ga ik zeker wat proberen te pielen hiermee 🙂
Het begin van de tutorial was een beetje saai, maar uiteindelijk een werkende app!
Hartelijk dank Wouter!
Wederom een prima tutorial. Er staat volgens mij alleen een klein foutje in: als je er i.v.m. een weekenddag een dag bij op moet tellen, staat er cal.add(Calendar.DAY_OF_MONTH,…. moet dit geen DAY_OF_WEEK zijn?
Ik ben ook nog niet klaar met dev tut 2, op de helft 🙁 was ziek!!
Ga het morgenavond lekker doen!
Bedankt weer
jee en ook weer aaah, ben nog niet klaar met dev tut 2…..
Ik ga deze week denk ik devtut 2 doen en dan daarna deze 3e. Was namelijk voor school ook bezig met iets anders programmeren en had niet echt zin om 2 dingen door elkaar te gaan doen. Maar ik ga ze zeker doen dus Wouter, blijf zou doorgaan zou ik zeggen 😉
Vandaag maar weer eens doen 🙂