onLayout(): mi ez, az elrendezés algoritmusa és a metódus paraméterei

Szerző: IT Sectr Megjelenés: 2026-07-22 Olvasási idő: 9 perc

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) — ViewGroup metódus, amely meghatározza a gyermek elemek pozícióit a szülő koordinátáiban
  • child.layout(l, t, r, b) — minden gyermek View számára történő hívás, amely beállítja annak végső határait
  • Layout fázis a measure fázis után és a draw fázis előtt következik a View életciklusában
  • getWidth() és getHeight() csak az onLayout végrehajtása után érhetőek el, ellentétben a getMeasuredWidth-vel, amely az onMeasure után érhető el
  • requestLayout() — metódus, amely a pozicionálást befolyásoló adatok megváltozásakor az onMeasure és onLayout újrahívását kezdeményezi

Mi az onLayout()?

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 végrehajtási folyamata

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.

Az onLayout paraméterei: l, t, r, b

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éterLeírásTipikus használat
l (left)A ViewGroup bal szélének koordinátája a szülőbenKezdőpont az X tengelyen a gyermek elemek számára
t (top)A ViewGroup felső szélének koordinátája a szülőbenKezdőpont az Y tengelyen a gyermek elemek számára
r (right)A ViewGroup jobb szélének koordinátája a szülőbenA szélesség felső határa, r - l = getWidth()
b (bottom)A ViewGroup alsó szélének koordinátája a szülőbenA magasság felső határa, b - t = getHeight()

Koordináták kiszámítása a gyermek elemek számára

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.

Egyedi ViewGroup példa onLayout-tal Kotlinban

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.

kotlin
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)
}

Különbségek az onLayout és onMeasure között

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ó.

Tipikus hibák az onLayout használata során

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

Kihagyható-e az onLayout felülírása ViewGroup-ban?

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.

Mi a különbség a layout és az onLayout között?

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.

Meghívhatja-e az onLayout a requestLayout-ot?

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.

Hogyan működik az onLayout az animációkkal?

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.

Miért nem hívódik meg az onLayout invalidate után?

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

  • onLayout() — ViewGroup metódus, amely meghatározza a gyermek View-k végleges pozícióit a mérési fázis befejezése után
  • child.layout(l, t, r, b) — a koordináták beállításának fő mechanizmusa minden gyermek elem számára
  • l, t, r, b paraméterek — a ViewGroup élkoordinátái a szülő rendszerben, szélesség = r - l, magasság = b - t
  • Layout fázis rekurzívan terjed a gyökér View-tól a gyermek elemekig, meghívva az onLayout-ot minden ViewGroup-ban
  • requestLayout() teljes ciklust indít a méretek és pozíciók újraszámítására, ellentétben az invalidate-tel, amely csak az újarajzolást indítja
  • A padding figyelembevétele az onLayout-ban kötelező — a gyermekek kezdeti koordinátáinak tartalmazniuk kell a szülő paddingLeft és paddingTop értékeit

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.

Projekt megbeszélése

Olvassa el is