onLayout() — je metoda třídy ViewGroup, která určuje pozice a rozměry dceřiných View na rovině souřadnic rodičovského kontejneru. Systém Android volá onLayout po fázi měření (onMeasure), když jsou pro každé dceřiné View již známy naměřená šířka a výška. Podle Android Developers Documentation (2026) je onLayout povinnou metodou pro přepsání v každé vlastní ViewGroup, protože standardní implementace ViewGroup neprovádí automatické pozicování potomků.
Hlavní body
onLayout(boolean changed, int l, int t, int r, int b) — je chráněná metoda třídy ViewGroup, kterou systém volá pro pozicování dceřiných View uvnitř rodičovského kontejneru. Vývojář tuto metodu přepisuje, když vytváří vlastní ViewGroup s nestandardním uspořádáním prvků: kaskádou, mříží, šachovnicovým vzorem nebo podle libovolných souřadnic. Každé dceřiné View získává své konečné hranice prostřednictvím volání child.layout().
Parametr changed udává, zda se pozice nebo velikost samotné ViewGroup změnila od posledního layout. Pokud je changed true, všechny dceřiné prvky pravděpodobně také vyžadují přepozicování. Parametry l, t, r, b jsou souřadnice levého horního a pravého dolního rohu ViewGroup v souřadném systému jejího rodiče. Uvnitř onLayout vývojář používá tyto hodnoty jako počáteční souřadnice pro rozmístění potomků.
ViewGroup — je jediná třída, která přepisuje onLayout. Běžný View (není ViewGroup) nemá dceřiné prvky a nepotřebuje onLayout — jeho vlastní pozicování je řešeno rodičovským kontejnerem. I když běžný View přepíše onLayout, systém jej nebude volat. To je základní rozdíl mezi onLayout a onMeasure, který je volán pro každý View.
Fáze layout začíná voláním veřejné metody layout(int l, int t, int r, int b) na kořenovém View. Tato metoda nastavuje konečné souřadnice samotného View a volá onLayout, pokud je View ViewGroup. Poté onLayout rekurzivně volá child.layout() pro každý dceřiný prvek a proces se opakuje směrem dolů hierarchií. Tímto způsobem se layout šíří od kořene k listům.
Před voláním onLayout systém zkontroluje, zda se rozměry View změnily oproti předchozímu cyklu. Pokud se rozměry nezměnily a requestLayout nebyl volán, onLayout nemusí být volán — systém použije výsledky předchozího layout. To je optimalizace, která zabraňuje zbytečným přepočtům pozic během animací nebo scrollování, když se mění pouze obsah, nikoli rozměry.
requestLayout() — je metoda View, která informuje systém, že layout View je zastaralý a vyžaduje přepočet. Volání requestLayout vede k úplnému cyklu: nejprve se volá onMeasure, poté onLayout, poté onDraw. Na rozdíl od invalidate, který spouští pouze překreslení, requestLayout spouští úplný přepočet rozměrů a pozic. Nadměrné volání requestLayout je častou příčinou problémů s výkonem.
l (left) — souřadnice X levého okraje ViewGroup v souřadném systému jejího rodiče. t (top) — souřadnice Y horního okraje. r (right) — souřadnice X pravého okraje. b (bottom) — souřadnice Y dolního okraje. Šířka ViewGroup se vypočítá jako r - l, výška jako b - t. Tyto souřadnice již zahrnují všechna padding samotné ViewGroup.
Uvnitř onLayout vývojář volá child.layout(int childLeft, int childTop, int childRight, int childBottom) pro každé dceřiné View. Souřadnice předané do child.layout musí být v souřadném systému rodičovské ViewGroup. Obvykle se childLeft a childTop vypočítávají s ohledem na padding rodiče: childLeft = l + paddingLeft + offsetX, childTop = t + paddingTop + offsetY.
| Parametr | Popis | Typické použití |
|---|---|---|
| l (left) | Souřadnice levého okraje ViewGroup v rodiči | Počáteční bod na ose X pro dceřiné prvky |
| t (top) | Souřadnice horního okraje ViewGroup v rodiči | Počáteční bod na ose Y pro dceřiné prvky |
| r (right) | Souřadnice pravého okraje ViewGroup v rodiči | Horní hranice šířky, r - l = getWidth() |
| b (bottom) | Souřadnice dolního okraje ViewGroup v rodiči | Horní hranice výšky, b - t = getHeight() |
Souřadnice potomka se vypočítají podle vzorce: childLeft = l + paddingLeft + (marginLeft if present), childRight = childLeft + child.getMeasuredWidth(). Podobně pro vertikálu: childTop = t + paddingTop + (marginTop), childBottom = childTop + child.getMeasuredHeight(). Po výpočtu těchto čtyř hodnot se zavolá child.layout(childLeft, childTop, childRight, childBottom).
Vytvořme FlowLayout — vlastní ViewGroup, která umísťuje dceřiné View do řádků, přesouvá prvky na nový řádek, když je aktuální řádek plný. Jedná se o analogii Flexbox s wrapem v jedné rovině. onLayout iteruje všechny dceřiné View, vypočítá pozici pro každý a volá child.layout() se správnými hranicemi.
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 a onLayout — jsou dvě po sobě jdoucí fáze životního cyklu View, které vykonávají zásadně odlišné úkoly. onMeasure určuje požadované (measured) rozměry View, zatímco onLayout nastavuje skutečné (actual) souřadnice a rozměry. Klíčový rozdíl: v onMeasure mohou být rozměry dočasné a později opravené rodičem, zatímco v onLayout je fixována konečná pozice každého dceřiného View.
onMeasure je volán pro každý View, včetně listových (TextView, ImageView, Button). onLayout je volán pouze pro ViewGroup. To je způsobeno tím, že pozicování je odpovědností rodičovského kontejneru, nikoli samotného View. Listový View získává svou pozici prostřednictvím layout() volaného z rodičovského onLayout.
getMeasuredWidth() a getMeasuredHeight() jsou k dispozici po onMeasure, zatímco getWidth() a getHeight() — až po onLayout. Pokud přistoupíte k getWidth() uvnitř onMeasure, bude vrácena hodnota z předchozího cyklu nebo nula. Proto pro výpočet rozměrů v onMeasure použijte MeasureSpec a postupně children.
Pozicování bez ohledu na padding — první chyba při implementaci onLayout. Vývojář často zapomíná přidat paddingLeft a paddingTop rodiče k počátečním souřadnicím dceřiných View. V důsledku toho se děti zobrazují na okraji ViewGroup, ignorující mezery nastavené pomocí setPadding() nebo v XML. Správný výpočet: childLeft = paddingLeft + offsetX.
Volání layout pro neviditelné potomky — druhý častý problém. Pokud ViewGroup obsahuje dceřiné View s visibility rovným GONE, není třeba je pozicovat — nezabírají místo. Nicméně onLayout musí tento případ správně zpracovat, přeskakovat GONE potomky. Pro INVISIBLE potomky je třeba layout stále volat — zachovávají své místo, i když nejsou zobrazeny.
Ignorování parametru changed — třetí chyba. Parametr changed udává, zda se rozměry nebo pozice ViewGroup změnily. Pokud changed == false, lze použít kešované souřadnice a nepřepočítávat layout všech dceřiných prvků. Úplné kešování layout je však obtížný úkol a většina implementací onLayout jednoduše přepočítává všechny prvky pokaždé. To je přijatelné při malém počtu potomků.
Často kladené otázky
Můžete, pokud ViewGroup používá standardní LayoutParams a nepřidává vlastní logiku pozicování. Standardní implementace onLayout ve ViewGroup však neprovádí žádnou akci — dceřiné prvky nebudou pozicovány. V praxi všechny ViewGroup (LinearLayout, RelativeLayout, FrameLayout) přepisují onLayout.
layout() — je veřejná konečná metoda View, volaná systémem nebo rodičovskou ViewGroup. Nastavuje souřadnice samotného View a volá onLayout, pokud je View ViewGroup. onLayout() — je chráněná metoda, kterou vývojář přepisuje pro vlastní uspořádání dceřiných prvků.
Technicky — ano, může. To je však kategoricky nedoporučeno, protože vede k nekonečné rekurzi: requestLayout → onMeasure → onLayout → requestLayout. Pokud je requestLayout zavolán uvnitř onLayout, systém vyhodí výjimku StackOverflowError. Všechny změny rozměrů by měly být provedeny před onLayout.
Layout animace (LayoutTransition) zachycují změny pozic dceřiných View a aplikují přechodovou animaci. Při zapnuté LayoutTransition onLayout nejprve nastaví konečné pozice a poté LayoutTransition animuje přesun ze staré pozice do nové. To vyžaduje správnou implementaci onLayout s odpovídajícími konečnými souřadnicemi.
invalidate() spouští pouze fázi draw (překreslení) a neovlivňuje measure a layout. Pro spuštění onLayout je třeba zavolat requestLayout(), který spouští úplný cyklus: measure → layout → draw. invalidate je účinnější pro aktualizaci vzhledu, když se rozměry a pozice nemění.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také