onLayout() est une méthode de la classe ViewGroup qui détermine les positions et tailles des vues enfants sur le plan de coordonnées du conteneur parent. Le système Android appelle onLayout après la phase de mesure (onMeasure), lorsque la largeur et la hauteur mesurées sont déjà connues pour chaque vue enfant. Selon la Documentation des Développeurs Android (2026), onLayout est une méthode obligatoire à surcharger dans toute ViewGroup personnalisée, car l'implémentation standard de ViewGroup n'effectue pas de positionnement automatique des enfants.
Points Clés
onLayout(boolean changed, int l, int t, int r, int b) est une méthode protected de la classe ViewGroup que le système appelle pour positionner les vues enfants à l'intérieur du conteneur parent. Le développeur surcharge cette méthode lorsqu'il crée une ViewGroup personnalisée avec un agencement non standard d'éléments : en cascade, en grille, en quinconce ou par coordonnées arbitraires. Chaque vue enfant reçoit ses limites finales via un appel à child.layout().
Le paramètre changed indique si la position ou la taille de la ViewGroup elle-même a changé depuis le dernier layout. Si changed est vrai, tous les éléments enfants ont probablement besoin d'un repositionnement. Les paramètres l, t, r, b sont les coordonnées des coins supérieur gauche et inférieur droit de la ViewGroup dans le système de coordonnées de son parent. À l'intérieur de onLayout, le développeur utilise ces valeurs comme coordonnées de départ pour la disposition des enfants.
ViewGroup est la seule classe qui surcharge onLayout. Une vue normale (pas une ViewGroup) n'a pas d'éléments enfants et n'a pas besoin de onLayout — son positionnement est géré par le conteneur parent. Même si une vue normale surcharge onLayout, le système ne l'appellera pas. C'est une différence fondamentale avec onMeasure, qui est appelé pour toute vue.
La phase layout commence par un appel à la méthode publique layout(int l, int t, int r, int b) sur la vue racine. Cette méthode définit les coordonnées finales de la vue elle-même et appelle onLayout si la vue est une ViewGroup. Ensuite, onLayout appelle récursivement child.layout() pour chaque élément enfant, et le processus se répète en descendant dans la hiérarchie. Ainsi, le layout se propage de la racine vers les feuilles.
Avant d'appeler onLayout, le système vérifie si les dimensions de la vue ont changé par rapport au cycle précédent. Si les dimensions n'ont pas changé et que requestLayout n'a pas été appelé, onLayout peut ne pas être appelé — le système utilise les résultats du layout précédent. C'est une optimisation qui évite des recalculs inutiles de positions pendant les animations ou le défilement, lorsque seul le contenu change mais pas les dimensions.
requestLayout() est une méthode de View qui notifie au système que le layout de la vue est obsolète et doit être recalculé. Appeler requestLayout déclenche un cycle complet : d'abord onMeasure est appelé, puis onLayout, puis onDraw. Contrairement à invalidate, qui ne déclenche que le redessin, requestLayout déclenche un recalcul complet des dimensions et des positions. Les appels excessifs à requestLayout sont une cause courante de problèmes de performance.
l (left) — la coordonnée X du bord gauche de la ViewGroup dans le système de coordonnées de son parent. t (top) — la coordonnée Y du bord supérieur. r (right) — la coordonnée X du bord droit. b (bottom) — la coordonnée Y du bord inférieur. La largeur de la ViewGroup est calculée comme r - l, la hauteur comme b - t. Ces coordonnées incluent déjà tout le padding de la ViewGroup elle-même.
À l'intérieur de onLayout, le développeur appelle child.layout(int childLeft, int childTop, int childRight, int childBottom) pour chaque vue enfant. Les coordonnées transmises à child.layout doivent être dans le système de coordonnées de la ViewGroup parente. Typiquement, childLeft et childTop sont calculés en tenant compte du padding du parent : childLeft = l + paddingLeft + offsetX, childTop = t + paddingTop + offsetY.
| Paramètre | Description | Utilisation typique |
|---|---|---|
| l (left) | Coordonnée du bord gauche de la ViewGroup dans le parent | Point de départ sur l'axe X pour les éléments enfants |
| t (top) | Coordonnée du bord supérieur de la ViewGroup dans le parent | Point de départ sur l'axe Y pour les éléments enfants |
| r (right) | Coordonnée du bord droit de la ViewGroup dans le parent | Limite supérieure de largeur, r - l = getWidth() |
| b (bottom) | Coordonnée du bord inférieur de la ViewGroup dans le parent | Limite supérieure de hauteur, b - t = getHeight() |
Les coordonnées enfants sont calculées par la formule : childLeft = l + paddingLeft + (marginLeft si présent), childRight = childLeft + child.getMeasuredWidth(). De même pour l'axe vertical : childTop = t + paddingTop + (marginTop), childBottom = childTop + child.getMeasuredHeight(). Après avoir calculé ces quatre valeurs, child.layout(childLeft, childTop, childRight, childBottom) est appelé.
Créons un FlowLayout — une ViewGroup personnalisée qui organise les vues enfants en lignes, en déplaçant les éléments vers une nouvelle ligne lorsque la ligne actuelle est pleine. C'est un équivalent de Flexbox avec wrap dans un seul plan. onLayout parcourt toutes les vues enfants, calcule la position pour chacune et appelle child.layout() avec les limites correctes.
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)
}
onMeasure et onLayout sont deux phases séquentielles du cycle de vie de View qui effectuent des tâches fondamentalement différentes. onMeasure détermine les dimensions souhaitées (mesurées) d'une vue, tandis que onLayout définit les coordonnées et dimensions réelles (finales). La différence clé : dans onMeasure, les dimensions peuvent être intermédiaires et ajustées ultérieurement par le parent, tandis que dans onLayout, la position finale de chaque vue enfant est fixée.
onMeasure est appelé pour chaque vue, y compris les vues feuilles (TextView, ImageView, Button). onLayout est appelé uniquement pour ViewGroup. Cela s'explique par le fait que le positionnement est la responsabilité du conteneur parent, pas de la vue elle-même. Une vue feuille reçoit sa position via layout() appelé depuis le onLayout du parent.
getMeasuredWidth() et getMeasuredHeight() sont disponibles après onMeasure, tandis que getWidth() et getHeight() ne sont disponibles qu'après onLayout. Si vous accédez à getWidth() dans onMeasure, il retournera la valeur du cycle précédent ou zéro. Par conséquent, pour calculer les dimensions dans onMeasure, vous devez utiliser MeasureSpec et les enfants séquentiellement.
Positionnement sans tenir compte du padding — la première erreur lors de l'implémentation de onLayout. Le développeur oublie souvent d'ajouter le paddingLeft et paddingTop du parent aux coordonnées initiales des vues enfants. En conséquence, les enfants s'affichent au bord de la ViewGroup, ignorant le padding défini via setPadding() ou dans le balisage XML. Calcul correct : childLeft = paddingLeft + offsetX.
Appeler layout pour les enfants invisibles — le deuxième problème courant. Si une ViewGroup contient des vues enfants avec une visibilité GONE, elles n'ont pas besoin d'être positionnées — elles ne prennent pas de place. Cependant, onLayout doit gérer correctement ce cas en ignorant les enfants GONE. Pour les enfants INVISIBLE, il faut toujours appeler layout — ils conservent leur place même s'ils ne sont pas affichés.
Ignorer le paramètre changed — la troisième erreur. Le paramètre changed indique si les dimensions ou la position de la ViewGroup ont changé. Si changed == false, des coordonnées mises en cache peuvent être utilisées sans recalculer le layout de tous les éléments enfants. Cependant, la mise en cache complète du layout est une tâche complexe, et dans la plupart des implémentations, onLayout recalcule simplement tous les éléments à chaque fois. C'est acceptable avec un petit nombre d'enfants.
Questions fréquentes
Oui, c'est possible si la ViewGroup utilise des LayoutParams standard et n'ajoute pas de logique de positionnement personnalisée. Cependant, l'implémentation standard de onLayout dans ViewGroup n'effectue aucune action — les éléments enfants ne seront pas positionnés. En pratique, toutes les ViewGroup (LinearLayout, RelativeLayout, FrameLayout) surchargent onLayout.
layout() est une méthode publique finale de View, appelée par le système ou la ViewGroup parente. Elle définit les coordonnées de la vue elle-même et appelle onLayout si la vue est une ViewGroup. onLayout() est une méthode protected que le développeur surcharge pour l'agencement personnalisé des éléments enfants.
Techniquement — oui, il le peut. Mais ce n'est absolument pas recommandé, car cela conduit à une récursion infinie : requestLayout → onMeasure → onLayout → requestLayout. Si requestLayout est appelé dans onLayout, le système lèvera une exception StackOverflowError. Toutes les modifications de dimensions doivent être effectuées avant onLayout.
Les animations de layout (LayoutTransition) interceptent les changements de positions des vues enfants et appliquent une animation de transition. Lorsque LayoutTransition est activé, onLayout définit d'abord les positions finales, puis LayoutTransition anime le déplacement de l'ancienne position vers la nouvelle. Cela nécessite une implémentation correcte de onLayout avec des coordonnées finales appropriées.
invalidate() ne déclenche que la phase draw (redessin), sans affecter measure et layout. Pour déclencher onLayout, vous devez appeler requestLayout(), qui initie le cycle complet : measure → layout → draw. invalidate est plus efficace pour mettre à jour l'apparence lorsque les dimensions et les positions ne changent pas.
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