onMeasure() est une méthode protégée de la classe android.view.View que le système Android appelle pour déterminer la taille d'une View. Le système transmet deux objets MeasureSpec à la méthode, chacun contenant un mode de mesure (EXACTLY, AT_MOST ou UNSPECIFIED) et une taille suggérée par le conteneur parent. Selon la Documentation Développeurs Android (2026), redéfinir onMeasure avec un traitement correct de MeasureSpec est une étape obligatoire pour toutes les vues et ViewGroups personnalisées nécessitant un contrôle précis de la taille.
Points clés
onMeasure(int widthMeasureSpec, int heightMeasureSpec) est une méthode de la classe View que le système Android appelle pour déterminer la largeur et la hauteur d'une vue. Le développeur redéfinit cette méthode pour spécifier la taille que la View doit avoir en fonction des contraintes transmises dans MeasureSpec. Sans une redéfinition correcte de onMeasure, une vue personnalisée peut s'afficher incorrectement ou ne pas apparaître du tout.
Le système appelle onMeasure pendant la phase measure du cycle de vie de la View, qui précède les phases de layout (onLayout) et de draw (onDraw). Si une View ne redéfinit pas onMeasure, l'implémentation de la superclasse est utilisée, qui définit des tailles par défaut basées sur le background drawable ou les layout_params. L'appel à super.onMeasure(widthMeasureSpec, heightMeasureSpec) ne fonctionne que pour les sous-classes standard de View, comme TextView ou ImageView.
Une exigence clé de onMeasure est que l'appel à setMeasuredDimension(int, int) doit être présent à la fin de la méthode. Si cet appel manque, le système lève une IllegalStateException indiquant que la View n'a pas défini les dimensions mesurées. Les dimensions finales deviennent disponibles via les accesseurs getMeasuredWidth() et getMeasuredHeight() après la fin de la phase measure.
MeasureSpec est un entier 32 bits dont les 2 bits supérieurs encodent le mode de mesure et les 30 bits inférieurs encodent la taille. Le mode détermine à quel point la View est libre de choisir sa propre taille. Android fournit trois modes : EXACTLY, AT_MOST et UNSPECIFIED. Chaque mode dicte une logique de traitement différente dans onMeasure.
| Mode MeasureSpec | Valeur | Comportement |
|---|---|---|
| EXACTLY | Le parent a spécifié une taille exacte | La View doit s'ajuster exactement à la taille donnée si elle ne veut pas dépasser les limites |
| AT_MOST | Le parent a défini une taille maximale | La View peut choisir n'importe quelle taille de 0 au maximum donné |
| UNSPECIFIED | Le parent n'impose aucune contrainte | La View peut choisir n'importe quelle taille souhaitée sans limite supérieure |
Pour extraire le mode et la taille de MeasureSpec, on utilise les méthodes statiques de la classe MeasureSpec : MeasureSpec.getMode(int) renvoie l'un des trois modes (EXACTLY, AT_MOST, UNSPECIFIED), et MeasureSpec.getSize(int) renvoie la taille numérique en pixels. Pour créer un MeasureSpec personnalisé, on utilise MeasureSpec.makeMeasureSpec(int size, int mode). Ces trois méthodes couvrent tous les scénarios de travail avec les tailles dans onMeasure.
Le modèle standard pour traiter MeasureSpec : si le mode est EXACTLY, utilisez la taille transmise comme finale ; si AT_MOST, choisissez le minimum entre la taille souhaitée (contenu de la View) et le maximum transmis ; si UNSPECIFIED, utilisez la taille souhaitée de la View sans contrainte. Ce modèle garantit un comportement correct sous toutes les contraintes du parent.
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)
}
Pour simplifier la logique standard, Android fournit la méthode resolveSizeAndState, qui prend la taille souhaitée, MeasureSpec et renvoie la taille finale avec le mode correct. Cette méthode implémente le modèle décrit ci-dessus en une seule ligne de code. La fonction resolveSize(int size, int measureSpec) est également disponible, renvoyant une taille propre sans bits d'état.
Android utilise un algorithme de mesure à deux passages qui garantit que chaque View dans la hiérarchie reçoit des dimensions correctes en tenant compte des contraintes du parent et des préférences des enfants. Au premier passage, le parent transmet MeasureSpec avec des contraintes aux vues enfants, et les vues enfants calculent leurs tailles souhaitées. Au deuxième passage, le parent prend la décision finale sur les tailles.
Pour ViewGroup, le processus de mesure est plus complexe : le parent doit d'abord mesurer tous ses enfants, puis déterminer sa propre taille en fonction de leurs tailles. L'appel à measureChildren(int widthMeasureSpec, int heightMeasureSpec) parcourt toutes les vues enfants et appelle measure(child, childWidthSpec, childHeightSpec) pour chacune. Après avoir mesuré tous les enfants, la ViewGroup appelle setMeasuredDimension avec ses propres dimensions.
Une nuance importante : la méthode measure (publique, finale) ne peut pas être redéfinie — c'est onMeasure qui est redéfini. Cela garantit que le système peut effectuer des tâches de maintenance avant et après onMeasure, telles que la vérification des changements de taille et le calcul de la zone sale pour le dessin ultérieur. Si une View a des dimensions fixes, la redéfinition de onMeasure peut ne pas être nécessaire.
MeasureSpec inclut non seulement la taille et le mode, mais aussi des bits d'état, accessibles via MeasureSpec.getMode(). Après avoir appelé setMeasuredDimension, l'état fait partie des dimensions mesurées de la View et peut être vérifié via getMeasuredState(). Ceci est utilisé dans ScrollView et d'autres conteneurs défilables pour transmettre correctement les contraintes aux enfants.
Examinons un exemple pratique de création d'une vue personnalisée avec onMeasure redéfini pour un affichage carré. La classe SquareView étend View et garantit que la largeur et la hauteur sont toujours égales, quel que soit le MeasureSpec transmis. Dans onMeasure, le côté minimal est déterminé et la taille carrée est définie.
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 nécessite une logique onMeasure plus complexe car les enfants doivent d'abord être mesurés, puis la taille de la ViewGroup elle-même est déterminée. L'exemple CascadeLayout distribue les enfants en cascade avec un décalage. Après avoir mesuré tous les enfants via measureChildWithMargins, la largeur et la hauteur totales sont calculées.
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
}
}
}
Appel manquant à setMeasuredDimension est l'erreur la plus courante. Si un développeur redéfinit onMeasure mais n'appelle pas setMeasuredDimension, l'application plante avec IllegalStateException. Cela arrive surtout lorsque la méthode a des branches conditionnelles et que l'une d'elles manque l'appel. Chaque branche de code dans onMeasure doit se terminer par un appel à setMeasuredDimension.
Ignorer le mode AT_MOST est la deuxième erreur la plus fréquente. Si une View en mode AT_MOST utilise toujours la taille transmise au lieu de calculer en fonction du contenu, le conteneur parent ne peut pas distribuer correctement l'espace. Par exemple, un TextView en AT_MOST doit calculer la largeur du texte et utiliser le minimum entre la largeur souhaitée et la largeur transmise. Ignorer AT_MOST fait que la View occupe tout l'espace disponible même avec un petit contenu.
Créer des objets dans onMeasure est une erreur de performance classique. Comme onMeasure peut être appelé plusieurs fois (à chaque demande de layout), créer des objets (Paint, Rect, String) dans cette méthode encombre la mémoire et déclenche le ramasse-miettes. Tous les objets doivent être créés une fois dans le constructeur de la View, et seule la logique de calcul des tailles doit s'exécuter dans onMeasure. La même règle s'applique à onDraw et onLayout.
measureChildWithMargins est une méthode protégée de ViewGroup qui mesure une seule vue enfant en tenant compte de ses MarginLayoutParams. La méthode accepte le MeasureSpec du parent et les décalages accumulés de largeur et de hauteur. Elle ajuste automatiquement le MeasureSpec pour la vue enfant en soustrayant les paddings du parent et les margins de l'enfant, puis transmet le MeasureSpec ajusté à child.measure().
Pour une logique de mesure avancée, une ViewGroup peut redéfinir measureChild(View child, int parentWidthSpec, int parentHeightSpec) ou travailler directement avec MeasureSpec pour chaque enfant. Par exemple, LinearLayout dans onMeasure parcourt toutes les vues enfants, mesure chacune en tenant compte de son layout_weight et distribue l'espace restant proportionnellement. Cette approche permet d'implémenter des algorithmes de mise en page arbitraires.
La mise en cache des résultats de mesure via le mécanisme de cache de mesure est disponible via le drapeau setMeasureWithLargestChildEnabled dans certaines ViewGroups. Cependant, dans la plupart des cas, onMeasure est rappelé à tout changement de layout et le cache ne s'applique pas. Dans les ViewGroups personnalisées, il est recommandé de minimiser les calculs dans onMeasure plutôt que de se fier au cache.
Questions fréquentes
Oui, si la vue personnalisée hérite directement de la classe View. Si elle hérite de TextView, ImageView ou Button avec leurs tailles standard, onMeasure peut rester inchangé. Pour ViewGroup, la redéfinition de onMeasure est toujours nécessaire — sinon les enfants ne seront pas mesurés correctement.
Le système Android lève IllegalStateException avec le message « The View did not call setMeasuredDimension ». Cette exception se produit dans la méthode measure() après la fin de onMeasure, si les dimensions finales sont restées nulles. L'exception plante l'application si elle n'est pas traitée via try-catch.
getMeasuredWidth() renvoie la taille définie dans onMeasure (phase de mesure). getWidth() renvoie la taille réelle que la View a reçue dans onLayout après tous les ajustements de positionnement. Pour la plupart des vues, ces valeurs coïncident, mais dans les ViewGroups personnalisées, elles peuvent différer.
Non, onMeasure est destiné exclusivement au calcul des tailles. Modifier l'état, lancer des animations, faire du réseau ou mettre à jour des données dans cette méthode viole l'architecture Android et peut entraîner des appels récursifs à measure, car les changements d'état peuvent déclencher requestLayout.
ConstraintLayout gère la mesure des enfants de manière indépendante en fonction des contraintes définies. Si une vue personnalisée dans ConstraintLayout redéfinit onMeasure, elle doit traiter correctement le MeasureSpec transmis par ConstraintLayout, sinon les contraintes peuvent ne pas fonctionner. ConstraintLayout utilise un algorithme à deux passages avec son propre WidgetContainer pour le calcul.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi