onLayout(): co to jest, algorytm rozmieszczania i parametry metody

Autor: IT Sectr Opublikowano: 2026-07-22 Czas czytania: 9 min

onLayout() — to metoda klasy ViewGroup, określająca pozycje i rozmiary widoków potomnych na płaszczyźnie współrzędnych kontenera nadrzędnego. System Android wywołuje onLayout po fazie pomiaru (onMeasure), gdy dla każdego widoku potomnego znane są już zmierzone szerokość i wysokość. Według Android Developers Documentation (2026), onLayout jest obowiązkową metodą do nadpisania w każdej niestandardowej ViewGroup, ponieważ standardowa implementacja ViewGroup nie wykonuje automatycznego pozycjonowania dzieci.

Najważniejsze

  • onLayout(boolean changed, int l, int t, int r, int b) — metoda ViewGroup określająca pozycje elementów potomnych we współrzędnych rodzica
  • child.layout(l, t, r, b) — wywołanie dla każdego widoku potomnego, ustawiające jego finalne granice
  • Faza layout następuje po fazie measure i przed fazą draw w cyklu życia View
  • getWidth() i getHeight() stają się dostępne dopiero po wykonaniu onLayout, w przeciwieństwie do getMeasuredWidth po onMeasure
  • requestLayout() — metoda inicjująca ponowne wywołanie onMeasure i onLayout przy zmianie danych wpływających na pozycjonowanie

Co to jest onLayout()?

onLayout(boolean changed, int l, int t, int r, int b) — to metoda protected klasy ViewGroup, która jest wywoływana przez system w celu pozycjonowania widoków potomnych wewnątrz kontenera nadrzędnego. Deweloper nadpisuje tę metodę, gdy tworzy niestandardową ViewGroup z niestandardowym rozmieszczeniem elementów: kaskadą, siatką, na przemian lub według dowolnych współrzędnych. Każdy widok potomny otrzymuje swoje finalne granice poprzez wywołanie child.layout().

Parametr changed wskazuje, czy pozycja lub rozmiar samej ViewGroup zmieniły się od ostatniego layout. Jeśli changed ma wartość true, wszystkie elementy potomne prawdopodobnie również wymagają zmiany pozycji. Parametry l, t, r, b — to współrzędne lewego górnego i prawego dolnego rogu ViewGroup w układzie współrzędnych jej rodzica. Wewnątrz onLayout deweloper używa tych wartości jako początkowych współrzędnych do rozmieszczania dzieci.

ViewGroup — to jedyna klasa, która nadpisuje onLayout. Zwykły View (nie ViewGroup) nie ma elementów potomnych i nie potrzebuje onLayout — jego własne pozycjonowanie jest obsługiwane przez kontener nadrzędny. Nawet jeśli zwykły View nadpisze onLayout, system nie będzie go wywoływać. To fundamentalna różnica między onLayout a onMeasure, który jest wywoływany dla każdego View.

Przebieg fazy layout

Faza layout rozpoczyna się od wywołania publicznej metody layout(int l, int t, int r, int b) na korzeniowym View. Ta metoda ustawia finalne współrzędne samego View i wywołuje onLayout, jeśli View jest ViewGroup. Następnie onLayout rekurencyjnie wywołuje child.layout() dla każdego elementu potomnego, a proces powtarza się w dół hierarchii. W ten sposób layout rozpowszechnia się od korzenia do liści.

Przed wywołaniem onLayout system sprawdza, czy rozmiary View nie zmieniły się w porównaniu z poprzednim cyklem. Jeśli rozmiary się nie zmieniły i nie został wywołany requestLayout, onLayout może nie być wywoływany — system używa wyników poprzedniego layout. To optymalizacja zapobiegająca zbędnym przeliczeniom pozycji podczas animacji lub scrollowania, gdy zmienia się tylko zawartość, a nie rozmiary.

requestLayout() — to metoda View, która informuje system, że layout View jest nieaktualny i wymaga ponownego przeliczenia. Wywołanie requestLayout prowadzi do pełnego cyklu: najpierw wywoływany jest onMeasure, następnie onLayout, a potem onDraw. W przeciwieństwie do invalidate, który uruchamia tylko przerysowanie, requestLayout uruchamia pełne przeliczenie rozmiarów i pozycji. Nadmierne wywoływanie requestLayout to częsta przyczyna problemów z wydajnością.

Parametry onLayout: l, t, r, b

l (left) — współrzędna X lewej krawędzi ViewGroup w układzie współrzędnych jej rodzica. t (top) — współrzędna Y górnej krawędzi. r (right) — współrzędna X prawej krawędzi. b (bottom) — współrzędna Y dolnej krawędzi. Szerokość ViewGroup oblicza się jako r - l, wysokość jako b - t. Te współrzędne obejmują już wszystkie marginesy wewnętrzne (padding) samej ViewGroup.

Wewnątrz onLayout deweloper wywołuje child.layout(int childLeft, int childTop, int childRight, int childBottom) dla każdego widoku potomnego. Współrzędne przekazywane do child.layout muszą być w układzie współrzędnych nadrzędnej ViewGroup. Zazwyczaj childLeft i childTop są obliczane z uwzględnieniem paddingów rodzica: childLeft = l + paddingLeft + offsetX, childTop = t + paddingTop + offsetY.

ParametrOpisTypowe zastosowanie
l (left)Współrzędna lewej krawędzi ViewGroup w rodzicuPunkt początkowy na osi X dla elementów potomnych
t (top)Współrzędna górnej krawędzi ViewGroup w rodzicuPunkt początkowy na osi Y dla elementów potomnych
r (right)Współrzędna prawej krawędzi ViewGroup w rodzicuGórna granica szerokości, r - l = getWidth()
b (bottom)Współrzędna dolnej krawędzi ViewGroup w rodzicuGórna granica wysokości, b - t = getHeight()

Obliczanie współrzędnych dla elementów potomnych

Współrzędne potomne oblicza się według wzoru: childLeft = l + paddingLeft + (marginLeft if present), childRight = childLeft + child.getMeasuredWidth(). Analogicznie w pionie: childTop = t + paddingTop + (marginTop), childBottom = childTop + child.getMeasuredHeight(). Po obliczeniu tych czterech wartości wywoływane jest child.layout(childLeft, childTop, childRight, childBottom).

Przykład niestandardowej ViewGroup z onLayout w Kotlin

Stwórzmy FlowLayout — niestandardową ViewGroup, która rozmieszcza widoki potomne w wierszach, przenosząc elementy do nowego wiersza, gdy bieżący wiersz jest pełny. To odpowiednik Flexbox z wrap w jednej płaszczyźnie. onLayout iteruje po wszystkich widokach potomnych, oblicza pozycję dla każdego i wywołuje child.layout() z poprawnymi granicami.

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

Różnice między onLayout a onMeasure

onMeasure i onLayout — to dwie kolejne fazy cyklu życia View, wykonujące zasadniczo różne zadania. onMeasure określa żądane (measured) rozmiary View, podczas gdy onLayout ustawia faktyczne (actual) współrzędne i rozmiary. Kluczowa różnica: w onMeasure rozmiary mogą być tymczasowe i później skorygowane przez rodzica, a w onLayout ustalana jest ostateczna pozycja każdego widoku potomnego.

onMeasure jest wywoływany dla każdego View, w tym liściowych (TextView, ImageView, Button). onLayout jest wywoływany tylko dla ViewGroup. Wynika to z faktu, że pozycjonowanie jest odpowiedzialnością kontenera nadrzędnego, a nie samego View. Liściowy View otrzymuje swoją pozycję przez layout() wywołany z nadrzędnego onLayout.

getMeasuredWidth() i getMeasuredHeight() są dostępne po onMeasure, a getWidth() i getHeight() — dopiero po onLayout. Jeśli odwołasz się do getWidth() wewnątrz onMeasure, zostanie zwrócona wartość z poprzedniego cyklu lub zero. Dlatego do obliczania rozmiarów w onMeasure należy używać MeasureSpec i kolejno children.

Typowe błędy przy pracy z onLayout

Pozycjonowanie bez uwzględnienia padding — pierwszy błąd przy implementacji onLayout. Deweloper często zapomina dodać paddingLeft i paddingTop rodzica do początkowych współrzędnych widoków potomnych. W rezultacie children wyświetlają się na krawędzi ViewGroup, ignorując marginesy wewnętrzne ustawione przez setPadding() lub w XML-u. Poprawne obliczenie: childLeft = paddingLeft + offsetX.

Wywoływanie layout dla niewidocznych dzieci — drugi częsty problem. Jeśli ViewGroup zawiera widoki potomne z visibility równym GONE, nie trzeba ich pozycjonować — nie zajmują miejsca. Jednak onLayout musi poprawnie obsługiwać ten przypadek, pomijając GONE-dzieci. Dla INVISIBLE children layout nadal trzeba wywoływać — zachowują swoje miejsce, choć nie są wyświetlane.

Ignorowanie parametru changed — trzeci błąd. Parametr changed wskazuje, czy rozmiary lub położenie ViewGroup uległy zmianie. Jeśli changed == false, można użyć buforowanych współrzędnych i nie przeliczać layoutu wszystkich elementów potomnych. Jednak pełne buforowanie layoutu to trudne zadanie i w większości implementacji onLayout po prostu przelicza wszystkie elementy za każdym razem. Jest to dopuszczalne przy małej liczbie dzieci.

Często zadawane pytania

Czy można nie nadpisywać onLayout w ViewGroup?

Można, jeśli ViewGroup używa standardowych LayoutParams i nie dodaje własnej logiki pozycjonowania. Jednak standardowa implementacja onLayout w ViewGroup nie wykonuje żadnych działań — elementy potomne nie zostaną pozycjonowane. W praktyce wszystkie ViewGroup (LinearLayout, RelativeLayout, FrameLayout) nadpisują onLayout.

Jaka jest różnica między layout a onLayout?

layout() — publiczna finalna metoda View, wywoływana przez system lub nadrzędną ViewGroup. Ustawia współrzędne samego View i wywołuje onLayout, jeśli View jest ViewGroup. onLayout() — metoda chroniona, którą deweloper nadpisuje w celu niestandardowego rozmieszczania elementów potomnych.

Czy onLayout może wywołać requestLayout?

Technicznie — tak, może. Ale jest to kategorycznie niezalecane, ponieważ prowadzi do nieskończonej rekurencji: requestLayout → onMeasure → onLayout → requestLayout. Jeśli wewnątrz onLayout zostanie wywołany requestLayout, system wyrzuci wyjątek StackOverflowError. Wszelkie zmiany rozmiarów powinny być wykonane przed onLayout.

Jak onLayout działa z animacjami?

Animacje layoutu (LayoutTransition) przechwytują zmiany pozycji widoków potomnych i stosują animację przejścia. Przy włączonej LayoutTransition onLayout najpierw ustawia finalne pozycje, a następnie LayoutTransition animuje przemieszczenie ze starej pozycji do nowej. Wymaga to prawidłowej implementacji onLayout z poprawnymi końcowymi współrzędnymi.

Dlaczego onLayout nie jest wywoływany po invalidate?

invalidate() uruchamia tylko fazę draw (przerysowanie), nie wpływając na measure i layout. Aby uruchomić onLayout, należy wywołać requestLayout(), który inicjuje pełny cykl: measure → layout → draw. invalidate jest bardziej efektywny do aktualizacji wyglądu, gdy rozmiary i pozycje się nie zmieniają.

Podsumowanie

  • onLayout() — metoda ViewGroup określająca finalne pozycje widoków potomnych po zakończeniu fazy pomiaru
  • child.layout(l, t, r, b) — główny mechanizm ustawiania współrzędnych dla każdego elementu potomnego
  • Parametry l, t, r, b — współrzędne krawędzi ViewGroup w układzie rodzica, szerokość = r - l, wysokość = b - t
  • Faza layout rekurencyjnie rozchodzi się od korzeniowego View do elementów potomnych, wywołując onLayout na każdej ViewGroup
  • requestLayout() uruchamia pełny cykl przeliczania rozmiarów i pozycji, w przeciwieństwie do invalidate, który uruchamia tylko przerysowanie
  • Uwzględnienie padding w onLayout jest obowiązkowe — początkowe współrzędne dzieci muszą zawierać paddingLeft i paddingTop rodzica

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również