onLayout() — a ViewGroup osztály metódusa, amely meghatározza a gyermek View-k pozícióit és méreteit a szülő konténer koordináta síkján. Az Android rendszer az onLayout-ot a mérési fázis (onMeasure) után hívja meg, amikor minden gyermek View számára már ismertek a mért szélesség és magasság értékek. Az Android Developers Documentation (2026) szerint az onLayout kötelező metódus minden egyedi ViewGroup-ban történő felülírásra, mivel a ViewGroup alapértelmezett megvalósítása nem végez automatikus gyermekpozicionálást.
Főbb pontok
onLayout(boolean changed, int l, int t, int r, int b) — a ViewGroup osztály protected metódusa, amelyet a rendszer a gyermek View-k szülő konténeren belül történő pozicionálására hív meg. A fejlesztő akkor írja felül ezt a metódust, amikor egyedi ViewGroup-ot hoz létre nem szabványos elem-elrendezéssel: kaszkád, rács, sakktábla minta vagy tetszőleges koordináták szerint. Minden gyermek View megkapja a végső határait a child.layout() híváson keresztül.
A changed paraméter azt jelzi, hogy a ViewGroup saját pozíciója vagy mérete megváltozott-e az utolsó layout óta. Ha a changed értéke true, akkor valószínűleg minden gyermek elem újrapozicionálást igényel. Az l, t, r, b paraméterek a ViewGroup bal felső és jobb alsó sarkának koordinátái a szülő koordináta-rendszerében. Az onLayout-on belül a fejlesztő ezeket az értékeket használja kezdeti koordinátaként a gyermekek elrendezéséhez.
ViewGroup — az egyetlen osztály, amely felülírja az onLayout-ot. Egy szokásos View (nem ViewGroup) nem rendelkezik gyermek elemekkel és nincs szüksége onLayout-ra — a saját pozicionálását a szülő konténer kezeli. Még ha egy szokásos View felül is írja az onLayout-ot, a rendszer nem fogja meghívni. Ez az alapvető különbség az onLayout és az onMeasure között, amelyet minden View számára meghívnak.
A layout fázis a gyökér View nyilvános layout(int l, int t, int r, int b) metódusának meghívásával kezdődik. Ez a metódus beállítja a View saját végső koordinátáit, és meghívja az onLayout-ot, ha a View ViewGroup. Ezután az onLayout rekurzívan meghívja a child.layout()-ot minden gyermek elem számára, és a folyamat lefelé halad a hierarchiában. Így a layout a gyökértől a levelek felé terjed.
Az onLayout meghívása előtt a rendszer ellenőrzi, hogy a View méretei megváltoztak-e az előző ciklushoz képest. Ha a méretek nem változtak és a requestLayout nem lett meghívva, az onLayout nem kerül meghívásra — a rendszer az előző layout eredményeit használja. Ez egy optimalizáció, amely megakadályozza a felesleges pozíció-újraszámításokat animációk vagy görgetés során, amikor csak a tartalom változik, a méretek nem.
requestLayout() — a View metódusa, amely értesíti a rendszert, hogy a View layout-ja elavult és újraszámítást igényel. A requestLayout meghívása teljes ciklushoz vezet: először az onMeasure, majd az onLayout, végül az onDraw kerül meghívásra. Ellentétben az invalidate-tel, amely csak az újarajzolást indítja el, a requestLayout elindítja a méretek és pozíciók teljes újraszámítását. A túlzott mértékű hívás requestLayout gyakori oka a teljesítményproblémáknak.
l (left) — a ViewGroup bal szélének X koordinátája a szülő koordináta-rendszerében. t (top) — a felső szél Y koordinátája. r (right) — a jobb szél X koordinátája. b (bottom) — az alsó szél Y koordinátája. A ViewGroup szélessége r - ként, magassága b - t-ként számítható ki. Ezek a koordináták már tartalmazzák a ViewGroup összes padding-értékét.
Az onLayout-on belül a fejlesztő meghívja a child.layout(int childLeft, int childTop, int childRight, int childBottom)-ot minden gyermek View számára. A child.layout-nek átadott koordinátáknak a szülő ViewGroup koordináta-rendszerében kell lenniük. Általában a childLeft és childTop értékeket a szülő padding-jének figyelembevételével számítják ki: childLeft = l + paddingLeft + offsetX, childTop = t + paddingTop + offsetY.
| Paraméter | Leírás | Tipikus használat |
|---|---|---|
| l (left) | A ViewGroup bal szélének koordinátája a szülőben | Kezdőpont az X tengelyen a gyermek elemek számára |
| t (top) | A ViewGroup felső szélének koordinátája a szülőben | Kezdőpont az Y tengelyen a gyermek elemek számára |
| r (right) | A ViewGroup jobb szélének koordinátája a szülőben | A szélesség felső határa, r - l = getWidth() |
| b (bottom) | A ViewGroup alsó szélének koordinátája a szülőben | A magasság felső határa, b - t = getHeight() |
Gyermek koordináták a következő képlet szerint számíthatók ki: childLeft = l + paddingLeft + (marginLeft if present), childRight = childLeft + child.getMeasuredWidth(). Hasonlóan a függőlegeshez: childTop = t + paddingTop + (marginTop), childBottom = childTop + child.getMeasuredHeight(). A négy érték kiszámítása után a child.layout(childLeft, childTop, childRight, childBottom) kerül meghívásra.
Hozzunk létre egy FlowLayout-ot — egy egyedi ViewGroup-ot, amely a gyermek View-kat sorokba rendezi, áthelyezve az elemeket egy új sorba, amikor az aktuális sor megtelik. Ez a Flexbox megfelelője wrap-pel egy síkban. Az onLayout végigiterál az összes gyermek View-n, kiszámítja mindegyik pozícióját és meghívja a child.layout()-ot a helyes határokkal.
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 és onLayout — két egymást követő fázis a View életciklusában, amelyek alapvetően különböző feladatokat látnak el. Az onMeasure meghatározza a View kívánt (measured) méreteit, míg az onLayout beállítja a tényleges (actual) koordinátákat és méreteket. A legfontosabb különbség: az onMeasure-ban a méretek ideiglenesek lehetnek és később a szülő által korrigálhatók, míg az onLayout-ban minden gyermek View végleges pozíciója rögzítésre kerül.
onMeasure minden View számára meghívásra kerül, beleértve a levél View-kat is (TextView, ImageView, Button). onLayout csak ViewGroup számára kerül meghívásra. Ennek az a magyarázata, hogy a pozicionálás a szülő konténer felelőssége, nem magé a View-é. A levél View a pozícióját a szülő onLayout-jából meghívott layout() révén kapja meg.
getMeasuredWidth() és getMeasuredHeight() az onMeasure után érhetők el, míg a getWidth() és getHeight() — csak az onLayout után. Ha a getWidth()-hez hozzáfér az onMeasure-on belül, az előző ciklus értéke vagy nulla kerül visszaadásra. Ezért a méretek kiszámításához az onMeasure-ban a MeasureSpec és egymás után a children használandó.
Pozicionálás a padding figyelembevétele nélkül — az első hiba az onLayout megvalósítása során. A fejlesztő gyakran elfelejti hozzáadni a szülő paddingLeft és paddingTop értékeit a gyermek View-k kezdeti koordinátáihoz. Ennek eredményeként a gyermekek a ViewGroup szélén jelennek meg, figyelmen kívül hagyva a setPadding() vagy XML útján beállított távolságokat. Helyes számítás: childLeft = paddingLeft + offsetX.
Layout meghívása láthatatlan gyermekek számára — a második gyakori probléma. Ha a ViewGroup GONE láthatóságú gyermek View-kat tartalmaz, azokat nem kell pozicionálni — nem foglalnak helyet. Azonban az onLayout-nak helyesen kell kezelnie ezt az esetet, kihagyva a GONE gyermekeket. Az INVISIBLE gyermekek számára a layout-ot továbbra is meg kell hívni — megtartják a helyüket, bár nem jelennek meg.
A changed paraméter figyelmen kívül hagyása — a harmadik hiba. A changed paraméter azt jelzi, hogy a ViewGroup méretei vagy pozíciója megváltoztak-e. Ha a changed == false, a gyorsítótárban tárolt koordináták használhatók és nem szükséges az összes gyermek elem layout-ját újraszámítani. Azonban a layout teljes gyorsítótárazása nehéz feladat, és a legtöbb onLayout megvalósítás egyszerűen minden alkalommal újraszámítja az összes elemet. Ez kis számú gyermek esetén elfogadható.
Gyakran Ismételt Kérdések
Igen, ha a ViewGroup szabványos LayoutParams-t használ és nem ad hozzá egyedi pozicionálási logikát. Azonban az onLayout alapértelmezett megvalósítása a ViewGroup-ban nem végez semmilyen műveletet — a gyermek elemek nem kerülnek pozicionálásra. A gyakorlatban minden ViewGroup (LinearLayout, RelativeLayout, FrameLayout) felülírja az onLayout-ot.
layout() — a View nyilvános végső metódusa, amelyet a rendszer vagy a szülő ViewGroup hív meg. Beállítja a View saját koordinátáit és meghívja az onLayout-ot, ha a View ViewGroup. onLayout() — protected metódus, amelyet a fejlesztő ír felül a gyermek elemek egyedi elrendezéséhez.
Technikailag — igen, meghívhatja. De ez kategorikusan nem ajánlott, mert végtelen rekurzióhoz vezet: requestLayout → onMeasure → onLayout → requestLayout. Ha az onLayout-on belül requestLayout kerül meghívásra, a rendszer StackOverflowError kivételt dob. Minden méretváltoztatást az onLayout előtt kell végrehajtani.
Layout animációk (LayoutTransition) elfogják a gyermek View-k pozícióváltozásait és átmeneti animációt alkalmaznak. Bekapcsolt LayoutTransition esetén az onLayout először beállítja a végső pozíciókat, majd a LayoutTransition animálja a mozgást a régi pozícióból az újba. Ehhez az onLayout helyes megvalósítása szükséges a megfelelő végkoordinátákkal.
invalidate() csak a draw fázist (újarajzolást) indítja el, nem befolyásolva a measure és layout fázisokat. Az onLayout elindításához a requestLayout()-ot kell meghívni, amely teljes ciklust indít: measure → layout → draw. Az invalidate hatékonyabb a megjelenés frissítésére, amikor a méretek és pozíciók nem változnak.
Összegzés
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is