onLayout(): ce este, algoritmul de aranjare și parametrii metodei

Autor: IT Sectr Publicat: 2026-07-22 Timp de citire: 9 min

onLayout() — este metoda clasei ViewGroup care determină pozițiile și dimensiunile View-urilor copil pe planul de coordonate al containerului părinte. Sistemul Android apelează onLayout după faza de măsurare (onMeasure), când pentru fiecare View copil sunt deja cunoscute lățimea și înălțimea măsurate. Conform Android Developers Documentation (2026), onLayout este o metodă obligatorie de suprascris în orice ViewGroup personalizată, deoarece implementarea standard a ViewGroup nu efectuează poziționarea automată a copiilor.

Principalele puncte

  • onLayout(boolean changed, int l, int t, int r, int b) — metoda ViewGroup care determină pozițiile elementelor copil în coordonatele părintelui
  • child.layout(l, t, r, b) — apelul pentru fiecare View copil care stabilește limitele sale finale
  • Faza layout urmează după faza measure și înainte de faza draw în ciclul de viață al View
  • getWidth() și getHeight() devin disponibile doar după executarea onLayout, spre deosebire de getMeasuredWidth după onMeasure
  • requestLayout() — metoda care inițiază re-apelarea onMeasure și onLayout la modificarea datelor care afectează poziționarea

Ce este onLayout()?

onLayout(boolean changed, int l, int t, int r, int b) — este o metodă protected a clasei ViewGroup care este apelată de sistem pentru poziționarea View-urilor copil în interiorul containerului părinte. Dezvoltatorul suprascrie această metodă atunci când creează o ViewGroup personalizată cu o aranjare nestandard a elementelor: în cascadă, grilă, în model de șah sau după coordonate arbitrare. Fiecare View copil își primește limitele finale prin apelul child.layout().

Parametrul changed indică dacă poziția sau dimensiunea ViewGroup înseși s-a schimbat de la ultimul layout. Dacă changed este true, toate elementele copil probabil au nevoie de repoziționare. Parametrii l, t, r, b sunt coordonatele colțului stânga-sus și dreapta-jos al ViewGroup în sistemul de coordonate al părintelui său. În interiorul onLayout, dezvoltatorul folosește aceste valori ca coordonate inițiale pentru aranjarea copiilor.

ViewGroup — este singura clasă care suprascrie onLayout. Un View obișnuit (nu ViewGroup) nu are elemente copil și nu are nevoie de onLayout — propria sa poziționare este gestionată de containerul părinte. Chiar dacă un View obișnuit suprascrie onLayout, sistemul nu îl va apela. Aceasta este diferența fundamentală între onLayout și onMeasure, care este apelat pentru orice View.

Fluxul de execuție al fazei layout

Faza layout începe cu apelarea metodei publice layout(int l, int t, int r, int b) pe View-ul rădăcină. Această metodă stabilește coordonatele finale ale View-țul însuși și apelează onLayout dacă View este un ViewGroup. Apoi onLayout apelează recursiv child.layout() pentru fiecare element copil, iar procesul se repetă în jos pe ierarhie. Astfel, layout se propagă de la rădăcină către frunze.

Înainte de apelarea onLayout, sistemul verifică dacă dimensiunile View s-au schimbat față de ciclul anterior. Dacă dimensiunile nu s-au schimbat și requestLayout nu a fost apelat, onLayout poate să nu fie apelat — sistemul folosește rezultatele layout-ului anterior. Aceasta este o optimizare care previne recalculările inutile ale pozițiilor în timpul animațiilor sau scroll-ului, când se schimbă doar conținutul, nu și dimensiunile.

requestLayout() — este o metodă View care informează sistemul că layout-ul View este expirat și necesită recalculare. Apelarea requestLayout duce la un ciclu complet: mai întâi se apelează onMeasure, apoi onLayout, apoi onDraw. Spre deosebire de invalidate, care declanșează doar redesenearea, requestLayout declanșează recalcularea completă a dimensiunilor și pozițiilor. Apelarea excesivă a requestLayout este o cauză frecventă a problemelor de performanță.

Parametrii onLayout: l, t, r, b

l (left) — coordonata X a marginii stângi a ViewGroup în sistemul de coordonate al părintelui său. t (top) — coordonata Y a marginii superioare. r (right) — coordonata X a marginii drepte. b (bottom) — coordonata Y a marginii inferioare. Lățimea ViewGroup se calculează ca r - l, înălțimea ca b - t. Aceste coordonate includ deja toate padding-urile ViewGroup înseși.

În interiorul onLayout, dezvoltatorul apelează child.layout(int childLeft, int childTop, int childRight, int childBottom) pentru fiecare View copil. Coordonatele transmise în child.layout trebuie să fie în sistemul de coordonate al ViewGroup părinte. De obicei, childLeft și childTop se calculează ținând cont de padding-urile părintelui: childLeft = l + paddingLeft + offsetX, childTop = t + paddingTop + offsetY.

ParametruDescriereUtilizare tipică
l (left)Coordonata marginii stângi a ViewGroup în părintePunctul de plecare pe axa X pentru elementele copil
t (top)Coordonata marginii superioare a ViewGroup în părintePunctul de plecare pe axa Y pentru elementele copil
r (right)Coordonata marginii drepte a ViewGroup în părinteLimita superioară a lățimii, r - l = getWidth()
b (bottom)Coordonata marginii inferioare a ViewGroup în părinteLimita superioară a înălțimii, b - t = getHeight()

Calculul coordonatelor pentru elementele copil

Coordonatele copil se calculează după formula: childLeft = l + paddingLeft + (marginLeft if present), childRight = childLeft + child.getMeasuredWidth(). Similar pentru verticală: childTop = t + paddingTop + (marginTop), childBottom = childTop + child.getMeasuredHeight(). După calcularea acestor patru valori, se apelează child.layout(childLeft, childTop, childRight, childBottom).

Exemplu de ViewGroup personalizată cu onLayout în Kotlin

Să creăm un FlowLayout — o ViewGroup personalizată care aranjează View-urile copil pe rânduri, transferând elementele pe un rând nou atunci când rândul curent este plin. Este analogul Flexbox cu wrap într-un singur plan. onLayout iterează prin toate View-urile copil, calculează poziția pentru fiecare și apelează child.layout() cu limitele corecte.

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

Diferențele dintre onLayout și onMeasure

onMeasure și onLayout — sunt două faze consecutive ale ciclului de viață al View care îndeplinesc sarcini fundamental diferite. onMeasure determină dimensiunile dorite (measured) ale View, în timp ce onLayout stabilește coordonatele și dimensiunile efective (actual). Diferența cheie: în onMeasure dimensiunile pot fi provizorii și corectate ulterior de părinte, iar în onLayout se fixează poziția finală a fiecărui View copil.

onMeasure este apelat pentru fiecare View, inclusiv pentru cele terminale (TextView, ImageView, Button). onLayout este apelat doar pentru ViewGroup. Aceasta se explică prin faptul că poziționarea este responsabilitatea containerului părinte, nu a View-țul însuși. View-țul terminal își primește poziția prin layout() apelat din onLayout părintelui.

getMeasuredWidth() și getMeasuredHeight() sunt disponibile după onMeasure, iar getWidth() și getHeight() — doar după onLayout. Dacă accesați getWidth() în interiorul onMeasure, va fi returnată valoarea din ciclul anterior sau zero. Prin urmare, pentru calcularea dimensiunilor în onMeasure trebuie să folosiți MeasureSpec și succesiv children.

Erori tipice la lucrul cu onLayout

Poziționarea fără a ține cont de padding — prima eroare la implementarea onLayout. Dezvoltatorul uită adesea să adauge paddingLeft și paddingTop ale părintelui la coordonatele inițiale ale View-urilor copil. Ca rezultat, copiii se afișează la marginea ViewGroup, ignorând spațiile stabilite prin setPadding() sau în XML. Calculul corect: childLeft = paddingLeft + offsetX.

Apelarea layout pentru copiii invizibili — a doua problemă frecventă. Dacă ViewGroup conține View-uri copil cu visibility egal cu GONE, acestea nu trebuie poziționate — nu ocupă spațiu. Totuși, onLayout trebuie să gestioneze corect acest caz, omițând copiii GONE. Pentru copiii INVISIBLE, layout trebuie totuși apelat — își păstrează locul, deși nu sunt afișați.

Ignorarea parametrului changed — a treia eroare. Parametrul changed indică dacă dimensiunile sau poziția ViewGroup s-au schimbat. Dacă changed == false, se pot folosi coordonatele cache și nu este necesară recalcularea layout-ului tuturor elementelor copil. Totuși, cache-ul complet al layout-ului este o sarcină dificilă și în majoritatea implementărilor onLayout pur și simplu recalculează toate elementele de fiecare dată. Acest lucru este acceptabil la un număr mic de copii.

Întrebări frecvente

Se poate să nu suprascriu onLayout în ViewGroup?

Se poate, dacă ViewGroup folosește LayoutParams standard și nu adaugă o logică personalizată de poziționare. Totuși, implementarea standard a onLayout în ViewGroup nu efectuează nicio acțiune — elementele copil nu vor fi poziționate. În practică, toate ViewGroup-urile (LinearLayout, RelativeLayout, FrameLayout) suprascriu onLayout.

Care este diferența dintre layout și onLayout?

layout() — este o metodă finală publică a View, apelată de sistem sau de ViewGroup părinte. Ea stabilește coordonatele View-țul însuși și apelează onLayout dacă View este un ViewGroup. onLayout() — este o metodă protected care este suprascrisă de dezvoltator pentru aranjarea personalizată a elementelor copil.

Poate onLayout să apeleze requestLayout?

Tehnic — da, poate. Dar acest lucru este categoric nerecomandat, deoarece duce la o recursie infinită: requestLayout → onMeasure → onLayout → requestLayout. Dacă în interiorul onLayout este apelat requestLayout, sistemul va arunca o excepție StackOverflowError. Toate modificările de dimensiuni trebuie efectuate înainte de onLayout.

Cum funcționează onLayout cu animațiile?

Animațiile de layout (LayoutTransition) interceptează modificările pozițiilor View-urilor copil și aplică o animație de tranziție. Cu LayoutTransition activată, onLayout stabilește mai întâi pozițiile finale, iar apoi LayoutTransition animează deplasarea din poziția veche în cea nouă. Aceasta necesită o implementare corectă a onLayout cu coordonate finale adecvate.

De ce onLayout nu este apelat după invalidate?

invalidate() declanșează doar faza draw (redesenarea), fără a afecta measure și layout. Pentru a declanșa onLayout, trebuie apelat requestLayout(), care inițiază un ciclu complet: measure → layout → draw. invalidate este mai eficient pentru actualizarea aspectului când dimensiunile și pozițiile nu se schimbă.

Concluzii

  • onLayout() — metoda ViewGroup care determină pozițiile finale ale View-urilor copil după finalizarea fazei de măsurare
  • child.layout(l, t, r, b) — mecanismul principal de stabilire a coordonatelor pentru fiecare element copil
  • Parametrii l, t, r, b — coordonatele marginilor ViewGroup în sistemul părintelui, lățimea = r - l, înălțimea = b - t
  • Faza layout se propagă recursiv de la View-ul rădăcină către elementele copil, apelând onLayout pe fiecare ViewGroup
  • requestLayout() declanșează un ciclu complet de recalculare a dimensiunilor și pozițiilor, spre deosebire de invalidate care declanșează doar redesenearea
  • Luarea în considerare a padding-ului în onLayout este obligatorie — coordonatele inițiale ale copiilor trebuie să includă paddingLeft și paddingTop ale părintelui

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și