onLayout() — is de methode van de klasse ViewGroup die de posities en afmetingen van kind-View's op het coördinatenvlak van de bovenliggende container bepaalt. Het Android-systeem roept onLayout aan na de meetfase (onMeasure), wanneer voor elke kind-View de gemeten breedte en hoogte al bekend zijn. Volgens de Android Developers Documentation (2026) is onLayout een verplichte methode om te overschrijven in elke aangepaste ViewGroup, omdat de standaardimplementatie van ViewGroup geen automatische positionering van kinderen uitvoert.
Belangrijkste punten
onLayout(boolean changed, int l, int t, int r, int b) — is een protected methode van de klasse ViewGroup die door het systeem wordt aangeroepen om kind-View's binnen de bovenliggende container te positioneren. De ontwikkelaar overschrijft deze methode wanneer hij een aangepaste ViewGroup maakt met een niet-standaard rangschikking van elementen: in cascade, raster, schaakbordpatroon of op willekeurige coördinaten. Elke kind-View ontvangt zijn uiteindelijke grenzen via de aanroep child.layout().
Parameter changed geeft aan of de positie of grootte van de ViewGroup zelf is veranderd sinds de laatste layout. Als changed waar is, moeten alle onderliggende elementen waarschijnlijk ook worden herpositioneerd. De parameters l, t, r, b zijn de coördinaten van de linker bovenhoek en rechter onderhoek van de ViewGroup in het coördinatenstelsel van de ouder. Binnen onLayout gebruikt de ontwikkelaar deze waarden als begincoördinaten voor het plaatsen van kinderen.
ViewGroup — is de enige klasse die onLayout overschrijft. Een gewone View (geen ViewGroup) heeft geen onderliggende elementen en heeft geen onLayout nodig — zijn eigen positionering wordt afgehandeld door de bovenliggende container. Zelfs als een gewone View onLayout overschrijft, zal het systeem deze niet aanroepen. Dit is het fundamentele verschil tussen onLayout en onMeasure, dat voor elke View wordt aangeroepen.
De layout-fase begint met de aanroep van de publieke methode layout(int l, int t, int r, int b) op de root-View. Deze methode stelt de uiteindelijke coördinaten van de View zelf in en roept onLayout aan als de View een ViewGroup is. Vervolgens roept onLayout recursief child.layout() aan voor elk onderliggend element, en het proces herhaalt zich naar beneden in de hiërarchie. Op deze manier verspreidt layout zich van de wortel naar de bladeren.
Vóór de aanroep van onLayout controleert het systeem of de afmetingen van de View zijn veranderd ten opzichte van de vorige cyclus. Als de afmetingen niet zijn veranderd en requestLayout niet is aangeroepen, wordt onLayout mogelijk niet aangeroepen — het systeem gebruikt de resultaten van de vorige layout. Dit is een optimalisatie die onnodige herberekeningen van posities tijdens animaties of scrollen voorkomt, wanneer alleen de inhoud verandert en niet de afmetingen.
requestLayout() — is een methode van View die het systeem informeert dat de layout van de View verouderd is en opnieuw moet worden berekend. Het aanroepen van requestLayout leidt tot een volledige cyclus: eerst wordt onMeasure aangeroepen, dan onLayout, dan onDraw. In tegenstelling tot invalidate, dat alleen het opnieuw tekenen start, start requestLayout de volledige herberekening van afmetingen en posities. Overmatig aanroepen van requestLayout is een veelvoorkomende oorzaak van prestatieproblemen.
l (left) — de X-coördinaat van de linkerrand van de ViewGroup in het coördinatenstelsel van de ouder. t (top) — de Y-coördinaat van de bovenrand. r (right) — de X-coördinaat van de rechterrand. b (bottom) — de Y-coördinaat van de onderrand. De breedte van de ViewGroup wordt berekend als r - l, de hoogte als b - t. Deze coördinaten omvatten reeds alle padding van de ViewGroup zelf.
Binnen onLayout roept de ontwikkelaar child.layout(int childLeft, int childTop, int childRight, int childBottom) aan voor elke kind-View. De coördinaten die aan child.layout worden doorgegeven, moeten in het coördinatenstelsel van de bovenliggende ViewGroup zijn. Gewoonlijk worden childLeft en childTop berekend rekening houdend met de padding van de ouder: childLeft = l + paddingLeft + offsetX, childTop = t + paddingTop + offsetY.
| Parameter | Beschrijving | Typisch gebruik |
|---|---|---|
| l (left) | Coördinaat van linkerrand ViewGroup in ouder | Startpunt op X-as voor onderliggende elementen |
| t (top) | Coördinaat van bovenrand ViewGroup in ouder | Startpunt op Y-as voor onderliggende elementen |
| r (right) | Coördinaat van rechterrand ViewGroup in ouder | Bovengrens breedte, r - l = getWidth() |
| b (bottom) | Coördinaat van onderrand ViewGroup in ouder | Bovengrens hoogte, b - t = getHeight() |
Coördinaten van kinderen worden berekend volgens de formule: childLeft = l + paddingLeft + (marginLeft if present), childRight = childLeft + child.getMeasuredWidth(). Analoog voor verticaal: childTop = t + paddingTop + (marginTop), childBottom = childTop + child.getMeasuredHeight(). Na het berekenen van deze vier waarden wordt child.layout(childLeft, childTop, childRight, childBottom) aangeroepen.
Laten we een FlowLayout maken — een aangepaste ViewGroup die kind-View's in rijen plaatst en elementen naar een nieuwe rij verplaatst wanneer de huidige rij vol is. Dit is het equivalent van Flexbox met wrap in één vlak. onLayout doorloopt alle kind-View's, berekent de positie voor elke en roept child.layout() aan met de juiste grenzen.
class FlowLayout(context: Context)
: ViewGroup(context) {
private val horizontalSpacing = 12
private val verticalSpacing = 8
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
val parentWidth =
MeasureSpec.getSize(widthMeasureSpec)
var rowX = paddingLeft
var rowY = paddingTop
var maxRowHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
measureChildWithMargins(child,
widthMeasureSpec, 0,
heightMeasureSpec, 0)
if (rowX + child.measuredWidth >
parentWidth - paddingRight) {
rowX = paddingLeft
rowY += maxRowHeight + verticalSpacing
maxRowHeight = 0
}
rowX += child.measuredWidth +
horizontalSpacing
maxRowHeight = maxOf(maxRowHeight,
child.measuredHeight)
}
val totalHeight = rowY + maxRowHeight +
paddingBottom
setMeasuredDimension(
resolveSize(parentWidth, widthMeasureSpec),
resolveSize(totalHeight, heightMeasureSpec))
}
override fun onLayout(changed: Boolean,
l: Int, t: Int,
r: Int, b: Int) {
val parentWidth = r - l
var rowX = paddingLeft
var rowY = paddingTop
var maxRowHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
val cw = child.measuredWidth
val ch = child.measuredHeight
if (rowX + cw >
parentWidth - paddingRight) {
rowX = paddingLeft
rowY += maxRowHeight + verticalSpacing
maxRowHeight = 0
}
child.layout(rowX, rowY,
rowX + cw, rowY + ch)
rowX += cw + horizontalSpacing
maxRowHeight =
maxOf(maxRowHeight, ch)
}
}
override fun generateLayoutParams(attrs: AttributeSet?)
: LayoutParams =
MarginLayoutParams(context, attrs)
}
onMeasure en onLayout — zijn twee opeenvolgende fasen in de levenscyclus van View die fundamenteel verschillende taken uitvoeren. onMeasure bepaalt de gewenste (measured) afmetingen van de View, terwijl onLayout de werkelijke (actual) coördinaten en afmetingen vaststelt. Het belangrijkste verschil: in onMeasure kunnen afmetingen tijdelijk zijn en later door de ouder worden gecorrigeerd, terwijl in onLayout de definitieve positie van elke kind-View wordt vastgelegd.
onMeasure wordt aangeroepen voor elke View, inclusief blad-Views (TextView, ImageView, Button). onLayout wordt alleen aangeroepen voor ViewGroup. Dit wordt verklaard doordat positionering de verantwoordelijkheid is van de bovenliggende container, niet van de View zelf. Een blad-View ontvangt zijn positie via layout() aangeroepen vanuit de onLayout van de ouder.
getMeasuredWidth() en getMeasuredHeight() zijn beschikbaar na onMeasure, terwijl getWidth() en getHeight() pas na onLayout beschikbaar zijn. Als u getWidth() binnen onMeasure aanroept, wordt de waarde van de vorige cyclus of nul teruggegeven. Daarom moet u voor het berekenen van afmetingen in onMeasure MeasureSpec en achtereenvolgens children gebruiken.
Positionering zonder rekening te houden met padding — de eerste fout bij het implementeren van onLayout. De ontwikkelaar vergeet vaak om paddingLeft en paddingTop van de ouder toe te voegen aan de begincoördinaten van kind-View's. Als gevolg worden children aan de rand van de ViewGroup weergegeven, waarbij de marges die zijn ingesteld via setPadding() of in XML worden genegeerd. Correcte berekening: childLeft = paddingLeft + offsetX.
Layout aanroepen voor onzichtbare kinderen — het tweede veelvoorkomende probleem. Als een ViewGroup kind-View's bevat met visibility gelijk aan GONE, hoeven deze niet te worden gepositioneerd — ze nemen geen ruimte in. Echter, onLayout moet dit geval correct afhandelen door GONE-kinderen over te slaan. Voor INVISIBLE-kinderen moet layout nog steeds worden aangeroepen — ze behouden hun plaats, hoewel ze niet worden weergegeven.
Het negeren van de parameter changed — de derde fout. De parameter changed geeft aan of de afmetingen of positie van de ViewGroup zijn veranderd. Als changed == false, kunnen gecachede coördinaten worden gebruikt en hoeft de layout van alle onderliggende elementen niet opnieuw te worden berekend. Echter, het volledig cachen van layout is een moeilijke taak en in de meeste implementaties van onLayout worden eenvoudigweg alle elementen elke keer opnieuw berekend. Dit is acceptabel bij een klein aantal kinderen.
Veelgestelde vragen
Ja, als de ViewGroup standaard LayoutParams gebruikt en geen aangepaste positioneringslogica toevoegt. Echter, de standaardimplementatie van onLayout in ViewGroup voert geen acties uit — onderliggende elementen worden niet gepositioneerd. In de praktijk overschrijven alle ViewGroups (LinearLayout, RelativeLayout, FrameLayout) onLayout.
layout() — is een publieke final methode van View, aangeroepen door het systeem of de bovenliggende ViewGroup. Het stelt de coördinaten van de View zelf in en roept onLayout aan als de View een ViewGroup is. onLayout() — is een protected methode die door de ontwikkelaar wordt overschreven voor het aangepast plaatsen van onderliggende elementen.
Technisch — ja, het kan. Maar dit wordt ten zeerste afgeraden omdat het leidt tot oneindige recursie: requestLayout → onMeasure → onLayout → requestLayout. Als requestLayout binnen onLayout wordt aangeroepen, gooit het systeem een StackOverflowError. Alle wijzigingen in afmetingen moeten vóór onLayout worden uitgevoerd.
Layout-animaties (LayoutTransition) onderscheppen wijzigingen in posities van kind-View's en passen een overgangsanimatie toe. Met ingeschakelde LayoutTransition stelt onLayout eerst de uiteindelijke posities in, waarna LayoutTransition de verplaatsing van de oude naar de nieuwe positie animeert. Dit vereist een correcte implementatie van onLayout met de juiste eindcoördinaten.
invalidate() start alleen de draw-fase (opnieuw tekenen) en heeft geen invloed op measure en layout. Om onLayout te activeren, moet requestLayout() worden aangeroepen, wat een volledige cyclus start: measure → layout → draw. invalidate is efficiënter voor het bijwerken van het uiterlijk wanneer afmetingen en posities niet veranderen.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook