onLayout(): шта је, алгоритам размештаја и параметри методе

Аутор: IT Sectr Објављено: 2026-07-22 Време читања: 9 мин

onLayout() — је метода класе ViewGroup која одређује позиције и величине потомака View на координатној равни родитељског контејнера. Android систем позива onLayout након фазе мерења (onMeasure), када су за сваки потомак View већ познате измерене ширина и висина. Према Android Developers Documentation (2026), onLayout је обавезна метода за прегазивање у свакој прилагођеној ViewGroup, јер стандардна имплементација ViewGroup не обавља аутоматско позиционирање деце.

Главно

  • onLayout(boolean changed, int l, int t, int r, int b) — метода ViewGroup која одређује позиције потомака у координатама родитеља
  • child.layout(l, t, r, b) — позив за сваки потомак View, који поставља његове коначне границе
  • Фаза layout следи након фазе measure и пре фазе draw у животном циклусу View
  • getWidth() и getHeight() постају доступни тек након извршења onLayout, за разлику од getMeasuredWidth након onMeasure
  • requestLayout() — метода која покреће поновно позивање onMeasure и onLayout при промени података који утичу на позиционирање

Шта је onLayout()?

onLayout(boolean changed, int l, int t, int r, int b) — је protected метода класе ViewGroup коју систем позива за позиционирање потомака View унутар родитељског контејнера. Програмер прегазива ову методу када ствара прилагођену ViewGroup са нестандардним распоредом елемената: каскадно, у мрежи, шаховски или по произвољним координатама. Сваки потомак View добија своје коначне границе путем позива child.layout().

Параметар changed указује да ли су се позиција или величина саме ViewGroup промењиле од претходног layout-а. Ако је changed true, сви потоми вероватно такође захтевају поновно позиционирање. Параметри l, t, r, b су координате горњег левог и доњег десног угла ViewGroup у координатном систему њеног родитеља. Унутар onLayout-а програмер користи ове вредности као почетне координате за распоређивање деце.

ViewGroup — је једина класа која прегазива onLayout. Обичан View (није ViewGroup) нема потомака и не захтева onLayout — његово сопствено позиционирање обрађује родитељски контејнер. Чак и ако обичан View прегази onLayout, систем га неће позвати. Ово је основна разлика између onLayout и onMeasure, који се позива за сваки View.

Ток извршења фазе layout

Фаза layout почиње позивом јавне методе layout(int l, int t, int r, int b) на коренском View-у. Ова метода поставља коначне координате самог View-а и позива onLayout ако је View ViewGroup. Затим onLayout рекурзивно позива child.layout() за сваки потомак, и процес се понавља низ хијерархију. На овај начин, layout се шири од корена ка лишћима.

Пре позивања onLayout, систем проверава да ли су се величине View-а промењиле у односу на претходни циклус. Ако се величине нису промењиле и requestLayout није позван, onLayout се неће позвати — систем користи резултате претходног layout-а. Ово је оптимизација која спречава непотребно прерачунавање позиција током анимација или скроловања, када се мења само садржај, а не величине.

requestLayout() — је метода View-а која обавештава систем да је layout View-а застарео и захтева поновно израчунавање. Позив requestLayout-а доводи до пуног циклуса: прво се позива onMeasure, затим onLayout, потом onDraw. За разлику од invalidate-а, који покреће само поновно исцртавање, requestLayout покреће пуно прерачунавање величина и позиција. Прекомерно позивање requestLayout-а је чест узрок проблема са перформансама.

Параметри onLayout: l, t, r, b

l (left) — X координата леве ивице ViewGroup у координатном систему њеног родитеља. t (top) — Y координата горње ивице. r (right) — X координата десне ивице. b (bottom) — Y координата доње ивице. Ширина ViewGroup се рачуна као r - l, висина као b - t. Ове координате већ укључују све padding-е саме ViewGroup.

Унутар onLayout, програмер позива child.layout(int childLeft, int childTop, int childRight, int childBottom) за сваки потомак View. Координате које се преносе у child.layout морају бити у координатном систему родитељске ViewGroup. Обично се childLeft и childTop израчунавају узимајући у обзир padding родитеља: childLeft = l + paddingLeft + offsetX, childTop = t + paddingTop + offsetY.

ПараметарОписТипична употреба
l (left)Координата леве ивице ViewGroup у родитељуПочетна тачка на X оси за потомке
t (top)Координата горње ивице ViewGroup у родитељуПочетна тачка на Y оси за потомке
r (right)Координата десне ивице ViewGroup у родитељуГорња граница ширине, r - l = getWidth()
b (bottom)Координата доње ивице ViewGroup у родитељуГорња граница висине, b - t = getHeight()

Израчунавање координата за потомке

Координате потомака се израчунавају по формули: childLeft = l + paddingLeft + (marginLeft if present), childRight = childLeft + child.getMeasuredWidth(). Слично за вертикалу: childTop = t + paddingTop + (marginTop), childBottom = childTop + child.getMeasuredHeight(). Након израчунавања ове четири вредности, позива се child.layout(childLeft, childTop, childRight, childBottom).

Пример прилагођене ViewGroup са onLayout у Kotlin-у

Направићемо FlowLayout — прилагођену ViewGroup која распоређује потомке View у редове, пребацујући елементе у нови ред када је текући ред пун. Ово је аналог функције Flexbox са wrap-ом у једној равни. onLayout пролази кроз све потомке View, израчунава положај за сваки и позива child.layout() са исправним границама.

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

Разлике између onLayout и onMeasure

onMeasure и onLayout — су две узастопне фазе животног циклуса View које обављају фундаментално различите задатке. onMeasure одређује жељене (measured) величине View-а, док onLayout поставља стварне (actual) координате и величине. Кључна разлика: у onMeasure величине могу бити привремене и касније кориговане од стране родитеља, а у onLayout се утврђује коначна позиција сваког потомка View.

onMeasure се позива за сваки View, укључујући листовне (TextView, ImageView, Button). onLayout се позива само за ViewGroup. Разлог је што је позиционирање одговорност родитељског контејнера, а не самог View-а. Листовни View добија своју позицију кроз layout() позван из родитељског onLayout-а.

getMeasuredWidth() и getMeasuredHeight() су доступни након onMeasure, а getWidth() и getHeight() — тек након onLayout. Ако приступите getWidth() унутар onMeasure, биће враћена вредност из претходног циклуса или нула. Стога за израчунавање величина у onMeasure треба користити MeasureSpec и редом children.

Типичне грешке у раду са onLayout

Позиционирање без узимања у обзир padding-а — прва грешка при имплементацији onLayout. Програмер често заборавља да дода paddingLeft и paddingTop родитеља почетним координатама потомака. Као резултат, children се приказују на ивици ViewGroup, игноришући размаке постављене путем setPadding() или у XML-у. Исправан израчун: childLeft = paddingLeft + offsetX.

Позивање layout-а за невидљиве потомке — други чест проблем. Ако ViewGroup садржи потомке View са visibility једнаким GONE, не треба их позиционирати — они не заузимају простор. Ипак, onLayout мора правилно да обради овај случај, прескачући GONE децу. За INVISIBLE children layout ипак треба позивати — они задржавају своје место, иако се не приказују.

Игнорисање параметра changed — трећа грешка. Параметар changed указује да ли су се величине или положај ViewGroup промењили. Ако је changed == false, могу се користити кеширане координате и не прерачунавати layout свих потомака. Међутим, пуно кеширање layout-а је тешк задатак и у већини имплементација onLayout једноставно поново израчунава све елементе сваки пут. Ово је прихватљиво код малог броја деце.

Често постављана питања

Могу ли да не прегазим onLayout у ViewGroup?

Можете, ако ViewGroup користи стандардне LayoutParams и не додаје прилагођену логику позиционирања. Ипак, стандардна имплементација onLayout у ViewGroup не обавља никакве радње — потомци неће бити позиционирани. У пракси, све ViewGroup (LinearLayout, RelativeLayout, FrameLayout) прегазивају onLayout.

Која је разлика између layout и onLayout?

layout() — је јавна финална метода View-а, коју позива систем или родитељска ViewGroup. Она поставља координате самог View-а и позива onLayout ако је View ViewGroup. onLayout() — је protected метода коју програмер прегазива за прилагођено распоређивање потомака.

Да ли onLayout може да позове requestLayout?

Технички — да, може. Али то је категорички непрепоручлјиво, јер доводи до бесконачне рекурзије: requestLayout → onMeasure → onLayout → requestLayout. Ако се унутар onLayout-а позове requestLayout, систем ће бацити изузетак StackOverflowError. Све промене величина морају бити извршене пре onLayout-а.

Како onLayout ради са анимацијама?

Layout анимације (LayoutTransition) пресрећу промене позиција потомака и примењују анимацију преласка. Када је LayoutTransition укључен, onLayout прво поставља коначне позиције, а затим LayoutTransition анимира помијање из старе у нову позицију. Ово захтева исправну имплементацију onLayout са исправним коначним координатама.

Зашто се onLayout не позива након invalidate?

invalidate() покреће само фазу draw (поновно исцртавање), не утичући на measure и layout. Да бисте позвали onLayout, потребно је позвати requestLayout() који покреће пун циклус: measure → layout → draw. invalidate је ефикаснији за ажурирање изгледа када се величине и позиције не мењају.

Закључак

  • onLayout() — метода ViewGroup која одређује коначне позиције потомака након завршетка фазе мерења
  • child.layout(l, t, r, b) — основни механизам постављања координата за сваки потомак
  • Параметри l, t, r, b — координате ивица ViewGroup у родитељском систему, ширина = r - l, висина = b - t
  • Фаза layout се рекурзивно шири од корена ка потомцима, позивајући onLayout на свакој ViewGroup
  • requestLayout() покреће пун циклус прерачунавања величина и позиција, за разлику од invalidate који покреће само поновно исцртавање
  • Узимање у обзир padding-а у onLayout је обавезно — почетне координате деце морају укључивати paddingLeft и paddingTop родитеља

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође