onMeasure() — este o metodă protected a clasei android.view.View, care este apelată de sistemul Android pentru a determina dimensiunile View-ului. Sistemul transmite metodei două obiecte MeasureSpec, fiecare dintre ele conținând modul de măsurare (EXACTLY, AT_MOST sau UNSPECIFIED) și dimensiunea propusă de containerul părinte. Conform Android Developers Documentation (2026), suprascrierea onMeasure cu gestionarea corectă a MeasureSpec este o etapă obligatorie pentru toate View-urile și ViewGroup-urile personalizate care necesită control precis asupra dimensiunilor.
Principalele puncte
onMeasure(int widthMeasureSpec, int heightMeasureSpec) — este metoda clasei View pe care sistemul Android o apelează pentru a determina lățimea și înălțimea vizualizării. Dezvoltatorul suprascrie această metodă pentru a indica ce dimensiune ar trebui să aibă View-ul pe baza constrângerilor transmise în MeasureSpec. Fără o suprascriere corectă a onMeasure, View-ul personalizat se poate afișa incorect sau poate să nu se afișeze deloc.
Sistemul apelează onMeasure în faza measure a ciclului de viață al View-ului, care precede fazele layout (onLayout) și draw (onDraw). Dacă View-ul nu suprascrie onMeasure, se utilizează implementarea din superclasă, care stabilește dimensiunile implicite pe baza background drawable sau layout_params. Apelul super.onMeasure(widthMeasureSpec, heightMeasureSpec) funcționează doar pentru subclasele standard de View, cum ar fi TextView sau ImageView.
Cerința cheie pentru onMeasure — apelul setMeasuredDimension(int, int) trebuie să fie prezent obligatoriu la sfârșitul metodei. Dacă acest apel lipsește, sistemul generează excepția IllegalStateException cu mesajul că View-ul nu a fixat dimensiunile măsurate. Dimensiunile finale devin disponibile prin getter-ele getMeasuredWidth() și getMeasuredHeight() după finalizarea fazei measure.
MeasureSpec — este un număr întreg pe 32 de biți, în care cei 2 biți superiori codifică modul de măsurare, iar cei 30 de biți inferiori — dimensiunea. Modul determină cât de liber este View-ul în alegerea dimensiunii sale. Android oferă trei moduri: EXACTLY, AT_MOST și UNSPECIFIED. Fiecare mod dictează o logică diferită de procesare în onMeasure.
| Mod MeasureSpec | Valoare | Comportament |
|---|---|---|
| EXACTLY | Părintele a specificat dimensiunea exactă | View-ul trebuie să se încadreze exact în dimensiunea transmisă, dacă nu vrea să iasă din limite |
| AT_MOST | Părintele a stabilit dimensiunea maximă | View-ul poate alege orice dimensiune de la 0 până la maximul transmis |
| UNSPECIFIED | Părintele nu impune restricții | View-ul poate alege orice dimensiune dorită fără limitări superioare |
Pentru extragerea modului și dimensiunii din MeasureSpec se folosesc metodele statice ale clasei MeasureSpec: MeasureSpec.getMode(int) returnează unul dintre cele trei moduri (EXACTLY, AT_MOST, UNSPECIFIED), iar MeasureSpec.getSize(int) returnează dimensiunea numerică în pixeli. Pentru a crea propriul MeasureSpec se folosește MeasureSpec.makeMeasureSpec(int size, int mode). Aceste trei metode acoperă toate scenariile de lucru cu dimensiunile în onMeasure.
Modelul standard de procesare a MeasureSpec: dacă modul este EXACTLY — utilizați dimensiunea transmisă ca finală; dacă este AT_MOST — alegeți minimul dintre dimensiunea dorită (conținutul View-ului) și maximul transmis; dacă este UNSPECIFIED — utilizați dimensiunea dorită a View-ului fără restricții. Acest model garantează un comportament corect sub orice constrângeri ale părintelui.
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)
}
Pentru simplificarea logicii standard, Android oferă metoda resolveSizeAndState, care primește dimensiunea dorită, MeasureSpec și returnează dimensiunea finală cu modul corect. Această metodă implementează modelul descris mai sus într-o singură linie de cod. De asemenea, este disponibilă funcția resolveSize(int size, int measureSpec), care returnează dimensiunea pură fără biți de stare.
Android utilizează un algoritm de măsurare în două treceri (two-pass measure), care garantează că fiecare View din ierarhie primește dimensiuni corecte ținând cont de constrângerile părintelui și preferințele elementelor copil. În prima trecere, părintele transmite View-urilor copil MeasureSpec cu constrângeri, iar View-urile copil își calculează dimensiunile dorite. În a doua trecere, părintele ia decizia finală privind dimensiunile.
Pentru ViewGroup, procesul de măsurare este mai complex: părintele trebuie mai întâi să măsoare toate elementele sale copil, apoi să își determine propria dimensiune pe baza dimensiunilor acestora. Apelul measureChildren(int widthMeasureSpec, int heightMeasureSpec) iterează prin toate View-urile copil și apelează pentru fiecare measure(child, childWidthSpec, childHeightSpec). După măsurarea tuturor elementelor copil, ViewGroup apelează setMeasuredDimension cu propriile dimensiuni.
Un aspect important: metoda measure (publică, final) nu poate fi suprascrisă — în locul ei se suprascrie onMeasure. Acest lucru garantează că sistemul poate executa acțiuni de serviciu înainte și după apelul onMeasure, cum ar fi verificarea modificării dimensiunilor și calculul zonei dirty pentru desenarea ulterioară. Dacă View-ul are dimensiuni fixe, suprascrierea onMeasure poate să nu fie necesară.
MeasureSpec include nu doar dimensiunea și modul, ci și biți de stare (state bits), accesibili prin MeasureSpec.getMode(). După apelul setMeasuredDimension, starea devine parte a dimensiunilor măsurate ale View-ului și poate fi verificată prin getMeasuredState(). Acest lucru este utilizat în ScrollView și alte containere derulabile pentru transmiterea corectă a constrângerilor către copii.
Să examinăm un exemplu practic de creare a unui View personalizat cu suprascrierea onMeasure pentru afișarea pătrată. Clasa SquareView moștenește View și garantează că lățimea și înălțimea sunt întotdeauna egale, indiferent de MeasureSpec-urile transmise. În onMeasure se determină latura mai mică și se stabilește dimensiunea pătrată.
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 necesită o logică onMeasure mai complexă, deoarece mai întâi se măsoară elementele copil, apoi se determină dimensiunea ViewGroup-ului însuși. Exemplul CascadeLayout distribuie elementele copil în cascadă cu spațiere. După măsurarea tuturor elementelor copil prin measureChildWithMargins, se calculează lățimea și înălțimea totală.
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
}
}
}
Omisiunea apelului setMeasuredDimension — cea mai frecventă greșeală. Dacă dezvoltatorul a suprascris onMeasure dar nu a apelat setMeasuredDimension, aplicația se va prăbuși cu IllegalStateException. Acest lucru se întâmplă mai ales când în metodă există ramificări condiționale și într-una dintre ramuri apelul lipsește. Fiecare ramură de cod din onMeasure trebuie să se termine cu apelul setMeasuredDimension.
Ignorarea modului AT_MOST — a doua cea mai frecventă greșeală. Dacă View-ul în modul AT_MOST folosește întotdeauna dimensiunea transmisă în loc de calculul pe baza conținutului, containerul părinte nu va putea distribui corect spațiul. De exemplu, TextView în AT_MOST ar trebui să calculeze lățimea textului și să folosească minimul dintre lățimea dorită și cea transmisă. Ignorarea AT_MOST duce la ocuparea întregului spațiu disponibil de către View chiar și cu conținut mic.
Crearea de obiecte în interiorul onMeasure — o greșeală clasică de performanță. Deoarece onMeasure poate fi apelat de multiple ori (la fiecare cerere de layout), crearea de obiecte (Paint, Rect, String) în interiorul acestei metode aglomerează memoria și provoacă colectarea de gunoi. Toate obiectele trebuie create o singură dată în constructorul View-ului, iar în onMeasure trebuie executată doar logica de calcul a dimensiunilor. Aceeași regulă se aplică pentru onDraw și onLayout.
measureChildWithMargins — este o metodă protected a ViewGroup care măsoară un View copil ținând cont de MarginLayoutParams-ul său. Metoda primește MeasureSpec-ul părintelui și deplasările acumulate pe lățime și înălțime. Ajustează automat MeasureSpec pentru View-ul copil, scăzând padding-urile părintelui și margin-ul copilului, și transmite MeasureSpec-ul ajustat către child.measure().
Pentru o logică de măsurare avansată, ViewGroup poate suprascrie measureChild(View child, int parentWidthSpec, int parentHeightSpec) sau poate lucra direct cu MeasureSpec pentru fiecare element copil. De exemplu, LinearLayout în onMeasure iterează prin toate View-urile copil, măsoară fiecare ținând cont de layout_weight-ul său și distribuie spațiul rămas proporțional. Această abordare permite implementarea unor algoritmi de layout arbitrari.
Cache-ul rezultatelor măsurătorii prin mecanismul de cache de măsurare (measure cache) este disponibil prin steagul setMeasureWithLargestChildEnabled în anumite ViewGroup-uri. Cu toate acestea, în majoritatea cazurilor onMeasure este apelat din nou la orice modificare de layout, iar cache-ul nu se aplică. În ViewGroup-urile personalizate se recomandă minimizarea calculelor în onMeasure, nu bazarea pe cache.
Întrebări frecvente
Da, dacă View-ul personalizat moștenește direct din clasa View. Dacă moștenirea este de la TextView, ImageView sau Button cu dimensiunile lor standard, onMeasure poate să nu fie suprascris. Pentru ViewGroup, suprascrierea onMeasure este întotdeauna necesară — altfel elementele copil nu vor fi măsurate corect.
Sistemul Android aruncă IllegalStateException cu textul „The View did not call setMeasuredDimension“. Această excepție apare în metoda measure() după finalizarea onMeasure, dacă dimensiunile finale au rămas zero. Excepția prăbușește aplicația dacă nu este gestionată prin try-catch.
getMeasuredWidth() returnează dimensiunea stabilită în onMeasure (faza de măsurare). getWidth() returnează dimensiunea reală pe care View-ul a primit-o în onLayout după luarea în considerare a tuturor ajustărilor de poziționare. Pentru majoritatea View-urilor, aceste valori coincid, dar în ViewGroup-urile personalizate pot fi diferite.
Nu, onMeasure este destinat exclusiv calculării dimensiunilor. Modificarea stării, pornirea animațiilor, lucrul cu rețeaua sau actualizarea datelor în această metodă încalcă arhitectura Android și poate duce la apeluri recursive measure, deoarece modificarea stării poate declanșa requestLayout.
ConstraintLayout gestionează independent măsurarea elementelor copil pe baza constrângerilor date. Dacă un View personalizat din interiorul ConstraintLayout suprascrie onMeasure, acesta trebuie să proceseze corect MeasureSpec-ul transmis de ConstraintLayout, altfel constrângerile pot să nu funcționeze. ConstraintLayout utilizează un algoritm în două treceri cu propriul WidgetContainer pentru calcul.
Rezumat
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.
Citiți și