onMeasure(): шта је то, режими MeasureSpec и превазилажење методе

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

onMeasure() — је protected метод класе android.view.View, који систем Android позива за одређивање димензија View-а. Систем прослеђује методи два објекта MeasureSpec, од којих сваки садржи режим мерења (EXACTLY, AT_MOST или UNSPECIFIED) и величину коју предлаže родитељски контејнер. Према Android Developers Documentation (2026), превазилажење onMeasure са коректном обрадом MeasureSpec је обавезна фаза за све прилагођене View-ове и ViewGroup-е који захтевају прецизну контролу над димензијама.

Главно

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec) — метод View-а за мерење димензија са прослеђивањем MeasureSpec-а од родитеља
  • MeasureSpec — 32-битна вредност која кодира режим мерења (UNSPECIFIED, EXACTLY, AT_MOST) и величину
  • setMeasuredDimension(int w, int h) — обавезан позив унутар onMeasure-а који фиксира коначне димензије View-а
  • Двопролазни алгоритам мерења: родитељ мери децу, затим деца пријављују своје димензије, и родитељ доноси коначну одлуку
  • measureChildWithMargins — помоћни метод за мерење деце View-ова у прилагођеним ViewGroup-ама

Шта је onMeasure()?

onMeasure(int widthMeasureSpec, int heightMeasureSpec) — је метод класе View који систем Android позива за одређивање ширине и висине приказа. Програмер превазилази овај метод да би назначио коју величину View треба да има на основу ограничења прослеђених у MeasureSpec-у. Без коректног превазилажења onMeasure-а, прилагођени View се може приказивати неправилно или се уопште не приказивати.

Систем позива onMeasure у фази measure животног циклуса View-а, која претходи фазама layout (onLayout) и draw (onDraw). Ако View не превазилази onMeasure, користи се имплементација из суперкласе која поставља подразумеване димензије на основу background drawable-а или layout_params-а. Позив super.onMeasure(widthMeasureSpec, heightMeasureSpec) ради само за стандардне подкласе View-а, као што су TextView или ImageView.

Кључни захтев за onMeasure — позив setMeasuredDimension(int, int) мора обавезно да буде присутан на крају метода. Ако овај позив недостаје, систем генерише изузетак IllegalStateException са поруком да View није фиксирао измерене димензије. Коначне димензије постају доступне преко getter-а getMeasuredWidth() и getMeasuredHeight() након завршетка фазе measure.

Режими MeasureSpec-а: три кључне вредности

MeasureSpec — је 32-битни цели број, у којем виша 2 бита кодирају режим мерења, а нижа 30 бита — величину. Режим одређује колико је View слободан у избору своје величине. Android пруžа три режима: EXACTLY, AT_MOST и UNSPECIFIED. Сваки режим налаže другу логику обраде у onMeasure-у.

Режим MeasureSpec-аВредностПонашање
EXACTLYРодитељ је навео тачну величинуView мора тачно да стане у прослеђену величину, ако не жели да изађе из граница
AT_MOSTРодитељ је поставио максималну величинуView може да изабере било коју величину од 0 до прослеђеног максимума
UNSPECIFIEDРодитељ не наmeće ограничењаView може да изабере било коју žељену величину без горњих ограничења

За извлачење режима и величине из MeasureSpec-а користе се статички методи класе MeasureSpec: MeasureSpec.getMode(int) враћа један од три режима (EXACTLY, AT_MOST, UNSPECIFIED), а MeasureSpec.getSize(int) враћа нумеричку величину у пикселима. За креирање сопственог MeasureSpec-а користи се MeasureSpec.makeMeasureSpec(int size, int mode). Ова три метода покривају све сценарије рада са димензијама у onMeasure-у.

Типична логика обраде MeasureSpec-а

Стандардни образац обраде MeasureSpec-а: ако је режим EXACTLY — користите прослеђену величину као коначну; ако је AT_MOST — изаберите минимум од žељене величине (садржаја View-а) и прослеђеног максимума; ако је UNSPECIFIED — користите žељену величину View-а без ограничења. Овај образац гарантује коректно понашање под било којим ограничењима родитеља.

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

Коришћење resolveSize-а

Ради поједностављења стандардне логике, Android пруžа метод resolveSizeAndState, који прима žељену величину, MeasureSpec и враћа коначну величину са коректним режимом. Овај метод имплементира горе описани образац у једном реду кода. Такође је доступна функција resolveSize(int size, int measureSpec), која враћа чисту величину без state-битова.

Двопролазни алгоритам мерења

Android користи двопролазни алгоритам мерења (two-pass measure), који гарантује да сваки View у хијерархији добија коректне димензије узимајући у обзир ограничења родитеља и преференције деце. У првом пролазу родитељ прослеђује деци View-овима MeasureSpec са ограничењима, а деца View-ови израчунавају своje žељене димензије. У другом пролазу родитељ доноси коначну одлуку о димензијама.

За ViewGroup процес мерења је сложенији: родитељ мора прво да измери сву своју децу, а затим да одреди сопствену величину на основу њихових димензија. Позив measureChildren(int widthMeasureSpec, int heightMeasureSpec) пролази кроз сву децу View-ове и позива за свако measure(child, childWidthSpec, childHeightSpec). Након мерења све деце, ViewGroup позива setMeasuredDimension са својим димензијама.

Важна нијанса: метод measure (публичан, final) се не може превазилазити — уместо њега се превазилази onMeasure. Ово гарантује да систем може да обавља услужне радње пре и после позива onMeasure-а, као што су провера промене димензија и израчунавање dirty-области за накнадно цртање. Ако View има фиксне димензије, превазилажење onMeasure-а можда неће бити потребно.

Застава MEASURED_SIZE_STATE

MeasureSpec укључује не само величину и режим, већ и битове стања (state bits), доступне преко MeasureSpec.getMode(). Након позива setMeasuredDimension, стање постаје део измерених димензија View-а и може се проверити преко getMeasuredState(). Ово се користи у ScrollView-у и другим контејнерима за померање ради коректног преношења ограничења деци.

Пример превазилажења onMeasure-а у Kotlin-у

Размотримо практични пример креирања прилагођеног View-а са превазилажењем onMeasure-а за квадратни приказ. Класа SquareView наслеђује View и гарантује да су ширина и висина увек једнаке, независно од прослеђених MeasureSpec-ова. У onMeasure-у се одређује мања страна и поставља се квадратна величина.

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

Прилагођена ViewGroup са мерењем деце

ViewGroup захтева сложенију логику onMeasure-а, јер се прво мере деца, затим се одређује величина саме ViewGroup-е. Пример CascadeLayout распоређује децу у каскади са размаком. Након мерења све деце преко measureChildWithMargins-а, израчунавају се укупна ширина и висина.

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

Типичне грешке при превазилажењу onMeasure-а

Пропуст позива setMeasuredDimension — најчешћа грешка. Ако је програмер превазишао onMeasure, али није позвао setMeasuredDimension, апликација ће пасти са IllegalStateException. Ово се посебно често дешава када у методи постоје условна гранања и у једној од грана позив недостаје. Свака грана кода у onMeasure-у мора да се заврши позивом setMeasuredDimension.

Игнорисање режима AT_MOST — друга по учесталости грешка. Ако View у режиму AT_MOST увек користи прослеђену величину уместо израчунавања на основу садржаја, родитељски контејнер неће моћи коректно да распореди простор. На пример, TextView у AT_MOST-у треба да израчуна ширину текста и користи минимум од žељене и прослеђене ширине. Игнорисање AT_MOST-а доводи до тога да View заузима сав доступни простор чак и при малом садржају.

Креирање објеката унутар onMeasure-а — класична грешка перформанси. Пошто се onMeasure може позивати више пута (при сваком захтеву за layout), креирање објеката (Paint, Rect, String) унутар овог метода затрпава меморију и изазива сакупљање смећа. Сви објекти треба да се креирају једном у конструктору View-а, а у onMeasure-у треба да се извршава само логика израчунавања димензија. Исто правило важи за onDraw и onLayout.

Мерење деце View-ова у ViewGroup-и

measureChildWithMargins — је заштићени метод ViewGroup-е који мери једно дете View узимајући у обзир његове MarginLayoutParams-е. Метод прима MeasureSpec родитеља и акумулирана померања по ширини и висини. Аутоматски коригује MeasureSpec за дете View, одузимајући padding-е родитеља и margin детета, и прослеђује кориговани MeasureSpec у child.measure().

За напредну логику мерења ViewGroup може да превазиђе measureChild(View child, int parentWidthSpec, int parentHeightSpec) или да директно ради са MeasureSpec-ом за свако дете. На пример, LinearLayout у onMeasure-у пролази кроз сву децу View-ове, мери свако узимајући у обзир његов layout_weight и распоређује преостали простор пропорционално. Овај приступ омогућава имплементацију произвољних алгоритама распореда.

Кеширање резултата мерења преко механизма кеша мерења (measure cache) доступно је преко заставе setMeasureWithLargestChildEnabled у одређеним ViewGroup-ама. Међутим, у већини случајева onMeasure се поново позива при било којој промени layout-а, и кеширање се не примењује. У прилагођеним ViewGroup-ама препоручује се минимизирање израчунавања у onMeasure-у, а не ослањање на кеш.

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

Да ли је обавезно превазилажење onMeasure-а за прилагођени View?

Да, ако прилагођени View наслеђује директно од класе View. Ако наслеђивање иде од TextView-а, ImageView-а или Button-а са њиховим стандардним димензијама, onMeasure се може прескочити. За ViewGroup превазилажење onMeasure-а је увек потребно — иначе деца неће бити коректно измерена.

Шта ће се десити ако се не позове setMeasuredDimension?

Систем Android баца IllegalStateException са текстом „The View did not call setMeasuredDimension“. Овај изузетак настаје у методи measure() након завршетка onMeasure-а, ако су коначне димензије остале нула. Изузетак руши апликацију ако није обрађен кроз try-catch.

Чиме се разликује getWidth од getMeasuredWidth-а?

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

Може ли се у onMeasure-у мењати анимације или стање View-а?

Не, onMeasure је намењен искључиво за израчунавање димензија. Мењање стања, покретање анимација, рад са мрежом или ажурирање података у овом методу нарушава архитектуру Android-а и може довести до рекурзивних позива measure-а, јер промена стања може да изазове requestLayout.

Како onMeasure интерагује са ConstraintLayout-ом?

ConstraintLayout самостално управља мерењем деце на основу датих ограничења. Ако прилагођени View унутар ConstraintLayout-а превазилази onMeasure, мора коректно да обради MeasureSpec прослеђен од ConstraintLayout-а, иначе ограничења можда неће радити. ConstraintLayout користи двопролазни алгоритам са сопственим WidgetContainer-ом за израчунавање.

Резиме

  • onMeasure() — метод View-а за одређивање димензија, који позива систем Android у фази measure
  • MeasureSpec кодира режим мерења (EXACTLY, AT_MOST, UNSPECIFIED) и величину прослеђену од родитеља
  • setMeasuredDimension — обавезан позив на крају onMeasure-а, којим се фиксирају коначне димензије
  • resolveSize — помоћни метод који имплементира стандардну логику обраде MeasureSpec-а у једном реду
  • Двопролазни алгоритам гарантује коректно мерење у хијерархији родитељ-деца
  • measureChildWithMargins се користи за мерење деце View-ова у прилагођеним ViewGroup-ама са узимањем у обзир размака
  • Креирање објеката унутар onMeasure-а се категорички не препоручује због ризика од GC и пропуста кадрова

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

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

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

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