onMeasure(): co to jest, tryby MeasureSpec i nadpisywanie metody

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

onMeasure() — to chroniona metoda klasy android.view.View, którą system Android wywołuje w celu określenia rozmiarów widoku. System przekazuje do metody dwa obiekty MeasureSpec, z których każdy zawiera tryb pomiaru (EXACTLY, AT_MOST lub UNSPECIFIED) i rozmiar proponowany przez kontener nadrzędny. Według Android Developers Documentation (2026), nadpisanie onMeasure z poprawną obsługą MeasureSpec to obowiązkowy etap dla wszystkich niestandardowych widoków i ViewGroup wymagających precyzyjnej kontroli nad rozmiarami.

Najważniejsze

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec) — metoda View do pomiaru rozmiarów z przekazaniem MeasureSpec od rodzica
  • MeasureSpec — 32-bitowa wartość kodująca tryb pomiaru (UNSPECIFIED, EXACTLY, AT_MOST) i rozmiar
  • setMeasuredDimension(int w, int h) — obowiązkowe wywołanie wewnątrz onMeasure, ustalające końcowe rozmiary View
  • Dwuprzebiegowy algorytm pomiaru: rodzic mierzy dzieci, następnie dzieci zgłaszają swoje rozmiary, a rodzic podejmuje ostateczną decyzję
  • measureChildWithMargins — metoda pomocnicza do pomiaru widoków potomnych w niestandardowych ViewGroup

Co to jest onMeasure()?

onMeasure(int widthMeasureSpec, int heightMeasureSpec) — to metoda klasy View, którą system Android wywołuje w celu określenia szerokości i wysokości widoku. Programista nadpisuje tę metodę, aby wskazać, jakiego rozmiaru powinien być View na podstawie ograniczeń przekazanych w MeasureSpec. Bez poprawnego nadpisania onMeasure niestandardowy View może wyświetlać się nieprawidłowo lub nie wyświetlać się wcale.

System wywołuje onMeasure w fazie measure cyklu życia View, która poprzedza fazy layout (onLayout) i draw (onDraw). Jeśli View nie nadpisuje onMeasure, używana jest implementacja z superklasy, która ustawia domyślne rozmiary na podstawie background drawable lub layout_params. Wywołanie super.onMeasure(widthMeasureSpec, heightMeasureSpec) działa tylko dla standardowych podklas View, takich jak TextView lub ImageView.

Kluczowym wymaganiem dla onMeasure jest wywołanie setMeasuredDimension(int, int) — musi ono koniecznie znajdować się na końcu metody. Jeśli to wywołanie jest nieobecne, system generuje wyjątek IllegalStateException z komunikatem, że View nie ustalił zmierzonych rozmiarów. Końcowe rozmiary stają się dostępne przez gettery getMeasuredWidth() i getMeasuredHeight() po zakończeniu fazy measure.

Tryby MeasureSpec: trzy kluczowe wartości

MeasureSpec — to 32-bitowa liczba całkowita, w której starsze 2 bity kodują tryb pomiaru, a młodsze 30 bitów — rozmiar. Tryb określa, jak bardzo View jest swobodny w wyborze swojego rozmiaru. Android udostępnia trzy tryby: EXACTLY, AT_MOST i UNSPECIFIED. Każdy tryb dyktuje inną logikę przetwarzania w onMeasure.

Tryb MeasureSpecWartośćZachowanie
EXACTLYRodzic określił dokładny rozmiarView musi dokładnie zmieścić się w przekazanym rozmiarze, jeśli nie chce wyjść poza granice
AT_MOSTRodzic ustawił maksymalny rozmiarView może wybrać dowolny rozmiar od 0 do przekazanego maksimum
UNSPECIFIEDRodzic nie nakłada ograniczeńView może wybrać dowolny żądany rozmiar bez ograniczeń górnych

Do wyodrębnienia trybu i rozmiaru z MeasureSpec używane są statyczne metody klasy MeasureSpec: MeasureSpec.getMode(int) zwraca jeden z trzech trybów (EXACTLY, AT_MOST, UNSPECIFIED), a MeasureSpec.getSize(int) zwraca liczbowy rozmiar w pikselach. Do utworzenia własnego MeasureSpec używane jest MeasureSpec.makeMeasureSpec(int size, int mode). Te trzy metody pokrywają wszystkie scenariusze pracy z rozmiarami w onMeasure.

Typowa logika przetwarzania MeasureSpec

Standardowy wzorzec przetwarzania MeasureSpec: jeśli tryb to EXACTLY — użyj przekazanego rozmiaru jako ostatecznego; jeśli AT_MOST — wybierz minimum z żądanego rozmiaru (zawartości View) i przekazanego maksimum; jeśli UNSPECIFIED — użyj żądanego rozmiaru View bez ograniczeń. Ten wzorzec gwarantuje poprawne zachowanie przy dowolnych ograniczeniach rodzica.

kotlin
override fun onMeasure(widthMeasureSpec: Int,
                       heightMeasureSpec: Int) {
    val desiredWidth = 200
    val desiredHeight = 100

    val widthMode = MeasureSpec.getMode(widthMeasureSpec)
    val widthSize = MeasureSpec.getSize(widthMeasureSpec)
    val heightMode = MeasureSpec.getMode(heightMeasureSpec)
    val heightSize = MeasureSpec.getSize(heightMeasureSpec)

    val width = when (widthMode) {
        MeasureSpec.EXACTLY -> widthSize
        MeasureSpec.AT_MOST -> minOf(desiredWidth, widthSize)
        else -> desiredWidth
    }
    val height = when (heightMode) {
        MeasureSpec.EXACTLY -> heightSize
        MeasureSpec.AT_MOST -> minOf(desiredHeight, heightSize)
        else -> desiredHeight
    }
    setMeasuredDimension(width, height)
}

Użycie resolveSize

W celu uproszczenia standardowej logiki Android udostępnia metodę resolveSizeAndState, która przyjmuje żądany rozmiar, MeasureSpec i zwraca końcowy rozmiar z poprawnym trybem. Ta metoda implementuje opisany powyżej wzorzec w jednej linii kodu. Dostępna jest również funkcja resolveSize(int size, int measureSpec), zwracająca czysty rozmiar bez bitów stanu.

Dwuprzebiegowy algorytm pomiaru

Android używa dwuprzebiegowego algorytmu pomiaru (two-pass measure), który gwarantuje, że każdy View w hierarchii otrzyma poprawne rozmiary z uwzględnieniem ograniczeń rodzica i preferencji elementów potomnych. W pierwszym przebiegu rodzic przekazuje widokom potomnym MeasureSpec z ograniczeniami, a widoki potomne obliczają swoje żądane rozmiary. W drugim przebiegu rodzic podejmuje ostateczną decyzję o rozmiarach.

Dla ViewGroup proces pomiaru jest bardziej złożony: rodzic musi najpierw zmierzyć wszystkie swoje elementy potomne, a następnie określić własny rozmiar na podstawie ich rozmiarów. Wywołanie measureChildren(int widthMeasureSpec, int heightMeasureSpec) iteruje przez wszystkie widoki potomne i wywołuje dla każdego measure(child, childWidthSpec, childHeightSpec). Po zmierzeniu wszystkich elementów potomnych ViewGroup wywołuje setMeasuredDimension ze swoimi rozmiarami.

Ważny niuans: metoda measure (publiczna, final) nie może być nadpisywana — zamiast niej nadpisywana jest onMeasure. Gwarantuje to, że system może wykonywać czynności pomocnicze przed i po wywołaniu onMeasure, takie jak sprawdzenie zmiany rozmiarów i obliczenie obszaru dirty do późniejszego rysowania. Jeśli View ma stałe rozmiary, nadpisywanie onMeasure może nie być wymagane.

Flaga MEASURED_SIZE_STATE

MeasureSpec zawiera nie tylko rozmiar i tryb, ale także bity stanu (state bits), dostępne przez MeasureSpec.getMode(). Po wywołaniu setMeasuredDimension stan staje się częścią zmierzonych rozmiarów View i może być sprawdzony przez getMeasuredState(). Jest to używane w ScrollView i innych kontenerach przewijanych do poprawnego przekazywania ograniczeń dzieciom.

Przykład nadpisywania onMeasure w Kotlin

Rozważmy praktyczny przykład tworzenia niestandardowego View z nadpisaniem onMeasure dla kwadratowego wyświetlania. Klasa SquareView dziedziczy po View i gwarantuje, że szerokość i wysokość są zawsze równe, niezależnie od przekazanych MeasureSpec. W onMeasure określany jest mniejszy bok i ustawiany jest kwadratowy rozmiar.

kotlin
class SquareView(context: Context)
    : View(context) {

    override fun onMeasure(widthMeasureSpec: Int,
                       heightMeasureSpec: Int) {
        val widthSize =
            MeasureSpec.getSize(widthMeasureSpec)
        val heightSize =
            MeasureSpec.getSize(heightMeasureSpec)
        val size = minOf(widthSize, heightSize)
        setMeasuredDimension(size, size)
    }
}

Niestandardowa ViewGroup z pomiarem elementów potomnych

ViewGroup wymaga bardziej złożonej logiki onMeasure, ponieważ najpierw mierzone są elementy potomne, następnie określany jest rozmiar samej ViewGroup. Przykład CascadeLayout rozmieszcza elementy potomne kaskadowo z odstępem. Po zmierzeniu wszystkich elementów potomnych przez measureChildWithMargins obliczane są całkowita szerokość i wysokość.

kotlin
class CascadeLayout(context: Context)
    : ViewGroup(context) {

    private val cascadeOffset = 40

    override fun onMeasure(widthMeasureSpec: Int,
                       heightMeasureSpec: Int) {
        var maxWidth = 0
        var totalHeight = 0
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            measureChildWithMargins(child,
                widthMeasureSpec,
                cascadeOffset * i,
                heightMeasureSpec, 0)
            maxWidth = maxOf(maxWidth,
                child.measuredWidth +
                cascadeOffset * i)
            totalHeight += child.measuredHeight
        }
        setMeasuredDimension(
            resolveSize(maxWidth, widthMeasureSpec),
            resolveSize(totalHeight, heightMeasureSpec))
    }

    override fun generateLayoutParams(attrs: AttributeSet?)
        : LayoutParams = MarginLayoutParams(context, attrs)

    override fun onLayout(changed: Boolean,
                       l: Int, t: Int,
                       r: Int, b: Int) {
        var top = t
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            val left = l + cascadeOffset * i
            child.layout(left, top,
                left + child.measuredWidth,
                top + child.measuredHeight)
            top += child.measuredHeight
        }
    }
}

Typowe błędy przy nadpisywaniu onMeasure

Pominięcie wywołania setMeasuredDimension — to najczęstszy błąd. Jeśli programista nadpisał onMeasure, ale nie wywołał setMeasuredDimension, aplikacja ulegnie awarii z IllegalStateException. Dzieje się tak szczególnie często, gdy w metodzie występują rozgałęzienia warunkowe i w jednej z gałęzi wywołanie jest nieobecne. Każda gałąź kodu w onMeasure musi kończyć się wywołaniem setMeasuredDimension.

Ignorowanie trybu AT_MOST — to drugi pod względem częstotliwości błąd. Jeśli View w trybie AT_MOST zawsze używa przekazanego rozmiaru zamiast obliczenia na podstawie zawartości, kontener nadrzędny nie będzie mógł poprawnie rozdzielić przestrzeni. Na przykład TextView w AT_MOST powinien obliczyć szerokość tekstu i użyć minimum z żądanej i przekazanej szerokości. Ignorowanie AT_MOST powoduje, że View zajmuje całą dostępną przestrzeń nawet przy małej zawartości.

Tworzenie obiektów wewnątrz onMeasure — klasyczny błąd wydajnościowy. Ponieważ onMeasure może być wywoływany wielokrotnie (przy każdym żądaniu layout), tworzenie obiektów (Paint, Rect, String) wewnątrz tej metody zaśmieca pamięć i prowokuje garbage collector. Wszystkie obiekty powinny być tworzone jednorazowo w konstruktorze View, a w onMeasure powinna być wykonywana tylko logika obliczania rozmiarów. Ta sama zasada dotyczy onDraw i onLayout.

Pomiar widoków potomnych w ViewGroup

measureChildWithMargins — to chroniona metoda ViewGroup, która mierzy jeden widok potomny z uwzględnieniem jego MarginLayoutParams. Metoda przyjmuje MeasureSpec rodzica i skumulowane przesunięcia w szerokości i wysokości. Automatycznie koryguje MeasureSpec dla widoku potomnego, odejmując wypełnienia rodzica i margines dziecka, i przekazuje skorygowany MeasureSpec do child.measure().

Dla zaawansowanej logiki pomiaru ViewGroup może nadpisać measureChild(View child, int parentWidthSpec, int parentHeightSpec) lub bezpośrednio pracować z MeasureSpec dla każdego elementu potomnego. Na przykład LinearLayout w onMeasure iteruje przez wszystkie widoki potomne, mierzy każdy z uwzględnieniem jego layout_weight i rozdziela pozostałą przestrzeń proporcjonalnie. To podejście pozwala na implementację dowolnych algorytmów układu.

Buforowanie wyników pomiaru przez mechanizm cache pomiarów (measure cache) jest dostępne przez flagę setMeasureWithLargestChildEnabled w niektórych ViewGroup. Jednak w większości przypadków onMeasure jest wywoływany ponownie przy każdej zmianie layout, a buforowanie nie jest stosowane. W niestandardowych ViewGroup zaleca się minimalizować obliczenia w onMeasure, a nie polegać na buforowaniu.

Często zadawane pytania

Czy konieczne jest nadpisywanie onMeasure dla niestandardowego View?

Tak, jeśli niestandardowy View dziedziczy bezpośrednio po klasie View. Jeśli dziedziczenie odbywa się po TextView, ImageView lub Button z ich standardowymi rozmiarami, onMeasure można pominąć. Dla ViewGroup nadpisywanie onMeasure jest zawsze wymagane — w przeciwnym razie elementy potomne nie zostaną poprawnie zmierzone.

Co się stanie, jeśli nie wywołam setMeasuredDimension?

System Android zgłasza IllegalStateException z tekstem „The View did not call setMeasuredDimension“. Ten wyjątek występuje w metodzie measure() po zakończeniu onMeasure, jeśli końcowe rozmiary pozostały zerowe. Wyjątek powoduje awarię aplikacji, jeśli nie jest obsłużony przez try-catch.

Czym różni się getWidth od getMeasuredWidth?

getMeasuredWidth() zwraca rozmiar ustawiony w onMeasure (faza pomiaru). getWidth() zwraca rzeczywisty rozmiar, który View otrzymał w onLayout po uwzględnieniu wszystkich poprawek pozycjonowania. Dla większości widoków te wartości są zgodne, ale w niestandardowych ViewGroup mogą się różnić.

Czy w onMeasure można zmieniać animacje lub stan View?

Nie, onMeasure jest przeznaczony wyłącznie do obliczania rozmiarów. Zmiana stanu, uruchamianie animacji, praca z siecią lub aktualizacja danych w tej metodzie narusza architekturę Androida i może prowadzić do rekurencyjnych wywołań measure, ponieważ zmiana stanu może wywołać requestLayout.

Jak onMeasure współdziała z ConstraintLayout?

ConstraintLayout samodzielnie zarządza pomiarem elementów potomnych na podstawie zadanych ograniczeń. Jeśli niestandardowy View wewnątrz ConstraintLayout nadpisuje onMeasure, musi poprawnie obsługiwać MeasureSpec przekazany przez ConstraintLayout, w przeciwnym razie ograniczenia mogą nie zadziałać. ConstraintLayout używa dwuprzebiegowego algorytmu z własnym WidgetContainer do obliczeń.

Podsumowanie

  • onMeasure() — metoda View do określania rozmiarów, wywoływana przez system Android w fazie measure
  • MeasureSpec koduje tryb pomiaru (EXACTLY, AT_MOST, UNSPECIFIED) i rozmiar przekazywany od rodzica
  • setMeasuredDimension — obowiązkowe wywołanie na końcu onMeasure, ustalające ostateczne rozmiary
  • resolveSize — metoda pomocnicza implementująca standardową logikę przetwarzania MeasureSpec w jednej linii
  • Dwuprzebiegowy algorytm gwarantuje poprawne pomiary w hierarchii rodzic-elementy potomne
  • measureChildWithMargins jest używany do pomiaru widoków potomnych w niestandardowych ViewGroup z uwzględnieniem odstępów
  • Tworzenie obiektów wewnątrz onMeasure jest kategorycznie odradzane ze względu na ryzyko GC i opóźnień klatek

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ż