onLayout() es un método de la clase ViewGroup que determina las posiciones y tamaños de las Views secundarias en el plano de coordenadas del contenedor padre. El sistema Android llama a onLayout después de la fase de medición (onMeasure), cuando ya se conocen el ancho y alto medidos de cada View secundaria. Según la Documentación para Desarrolladores de Android (2026), onLayout es un método obligatorio de sobrescribir en cualquier ViewGroup personalizada, ya que la implementación estándar de ViewGroup no realiza el posicionamiento automático de los hijos.
Puntos Clave
onLayout(boolean changed, int l, int t, int r, int b) es un método protected de la clase ViewGroup que el sistema llama para posicionar las Views secundarias dentro del contenedor padre. El desarrollador sobrescribe este método cuando crea una ViewGroup personalizada con una disposición no estándar de elementos: en cascada, cuadrícula, patrón de tablero de ajedrez o por coordenadas arbitrarias. Cada View secundaria recibe sus límites finales a través de una llamada a child.layout().
El parámetro changed indica si la posición o el tamaño de la propia ViewGroup ha cambiado desde el último layout. Si changed es true, todos los elementos secundarios probablemente también necesitan ser reposicionados. Los parámetros l, t, r, b son las coordenadas de las esquinas superior izquierda e inferior derecha de la ViewGroup en el sistema de coordenadas de su padre. Dentro de onLayout, el desarrollador usa estos valores como coordenadas iniciales para la disposición de los hijos.
ViewGroup es la única clase que sobrescribe onLayout. Una View normal (que no es ViewGroup) no tiene elementos secundarios y no necesita onLayout — su posicionamiento es manejado por el contenedor padre. Incluso si una View normal sobrescribe onLayout, el sistema no lo llamará. Esta es una diferencia fundamental con onMeasure, que se llama para cualquier View.
La fase layout comienza con una llamada al método público layout(int l, int t, int r, int b) en la View raíz. Este método establece las coordenadas finales de la propia View y llama a onLayout si la View es una ViewGroup. Luego onLayout llama recursivamente a child.layout() para cada elemento secundario, y el proceso se repite hacia abajo en la jerarquía. Así, layout se propaga desde la raíz hasta las hojas.
Antes de llamar a onLayout, el sistema verifica si las dimensiones de la View han cambiado en comparación con el ciclo anterior. Si las dimensiones no han cambiado y no se ha llamado a requestLayout, es posible que onLayout no se llame — el sistema usa los resultados del layout anterior. Esta es una optimización que evita recálculos innecesarios de posiciones durante animaciones o desplazamiento, cuando solo cambia el contenido pero no las dimensiones.
requestLayout() es un método de View que notifica al sistema que el layout de la View está desactualizado y necesita ser recalculado. Llamar a requestLayout desencadena un ciclo completo: primero se llama a onMeasure, luego a onLayout, luego a onDraw. A diferencia de invalidate, que solo activa el redibujado, requestLayout activa un recálculo completo de dimensiones y posiciones. Las llamadas excesivas a requestLayout son una causa común de problemas de rendimiento.
l (left) — la coordenada X del borde izquierdo de la ViewGroup en el sistema de coordenadas de su padre. t (top) — la coordenada Y del borde superior. r (right) — la coordenada X del borde derecho. b (bottom) — la coordenada Y del borde inferior. El ancho de la ViewGroup se calcula como r - l, la altura como b - t. Estas coordenadas ya incluyen todo el padding de la propia ViewGroup.
Dentro de onLayout, el desarrollador llama a child.layout(int childLeft, int childTop, int childRight, int childBottom) para cada View secundaria. Las coordenadas pasadas a child.layout deben estar en el sistema de coordenadas de la ViewGroup padre. Normalmente childLeft y childTop se calculan teniendo en cuenta el padding del padre: childLeft = l + paddingLeft + offsetX, childTop = t + paddingTop + offsetY.
| Parámetro | Descripción | Uso Típico |
|---|---|---|
| l (left) | Coordenada del borde izquierdo de la ViewGroup en el padre | Punto inicial en el eje X para elementos secundarios |
| t (top) | Coordenada del borde superior de la ViewGroup en el padre | Punto inicial en el eje Y para elementos secundarios |
| r (right) | Coordenada del borde derecho de la ViewGroup en el padre | Límite superior de ancho, r - l = getWidth() |
| b (bottom) | Coordenada del borde inferior de la ViewGroup en el padre | Límite superior de altura, b - t = getHeight() |
Las coordenadas secundarias se calculan mediante la fórmula: childLeft = l + paddingLeft + (marginLeft si existe), childRight = childLeft + child.getMeasuredWidth(). De manera similar para el eje vertical: childTop = t + paddingTop + (marginTop), childBottom = childTop + child.getMeasuredHeight(). Después de calcular estos cuatro valores, se llama a child.layout(childLeft, childTop, childRight, childBottom).
Creemos un FlowLayout — una ViewGroup personalizada que organiza las Views secundarias en filas, moviendo los elementos a una nueva línea cuando la fila actual está llena. Es un equivalente de Flexbox con wrap en un solo plano. onLayout itera sobre todas las Views secundarias, calcula la posición de cada una y llama a child.layout() con los límites correctos.
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 y onLayout son dos fases secuenciales del ciclo de vida de View que realizan tareas fundamentalmente diferentes. onMeasure determina las dimensiones deseadas (medidas) de una View, mientras que onLayout establece las coordenadas y dimensiones reales (finales). La diferencia clave: en onMeasure, las dimensiones pueden ser intermedias y luego ajustadas por el padre, mientras que en onLayout se fija la posición final de cada View secundaria.
onMeasure se llama para cada View, incluidas las Views hoja (TextView, ImageView, Button). onLayout se llama solo para ViewGroup. Esto se debe a que el posicionamiento es responsabilidad del contenedor padre, no de la propia View. Una View hoja recibe su posición a través de layout() llamado desde el onLayout del padre.
getMeasuredWidth() y getMeasuredHeight() están disponibles después de onMeasure, mientras que getWidth() y getHeight() solo están disponibles después de onLayout. Si accede a getWidth() dentro de onMeasure, devolverá el valor del ciclo anterior o cero. Por lo tanto, para calcular dimensiones en onMeasure, debe usar MeasureSpec y los hijos secuencialmente.
Posicionamiento sin tener en cuenta el padding — el primer error al implementar onLayout. El desarrollador a menudo olvida agregar el paddingLeft y paddingTop del padre a las coordenadas iniciales de las Views secundarias. Como resultado, los hijos se muestran en el borde de la ViewGroup, ignorando el padding establecido mediante setPadding() o en el marcado XML. Cálculo correcto: childLeft = paddingLeft + offsetX.
Llamar a layout para hijos invisibles — el segundo problema común. Si una ViewGroup contiene Views secundarias con visibilidad GONE, no es necesario posicionarlas — no ocupan espacio. Sin embargo, onLayout debe manejar este caso correctamente, omitiendo los hijos GONE. Para los hijos INVISIBLE, todavía es necesario llamar a layout — conservan su espacio aunque no se muestren.
Ignorar el parámetro changed — el tercer error. El parámetro changed indica si las dimensiones o la posición de la ViewGroup han cambiado. Si changed == false, se pueden usar coordenadas almacenadas en caché sin recalcular el layout de todos los elementos secundarios. Sin embargo, el almacenamiento completo en caché del layout es una tarea compleja, y en la mayoría de las implementaciones onLayout simplemente recalcula todos los elementos cada vez. Esto es aceptable con un número pequeño de hijos.
Preguntas Frecuentes
Sí, es posible si la ViewGroup usa LayoutParams estándar y no agrega lógica de posicionamiento personalizada. Sin embargo, la implementación estándar de onLayout en ViewGroup no realiza ninguna acción — los elementos secundarios no serán posicionados. En la práctica, todas las ViewGroup (LinearLayout, RelativeLayout, FrameLayout) sobrescriben onLayout.
layout() es un método público final de View, llamado por el sistema o la ViewGroup padre. Establece las coordenadas de la propia View y llama a onLayout si la View es una ViewGroup. onLayout() es un método protected que el desarrollador sobrescribe para la disposición personalizada de elementos secundarios.
Técnicamente — sí, puede. Pero esto no se recomienda en absoluto, ya que conduce a una recursión infinita: requestLayout → onMeasure → onLayout → requestLayout. Si se llama a requestLayout dentro de onLayout, el sistema lanzará una excepción StackOverflowError. Todos los cambios de dimensiones deben realizarse antes de onLayout.
Las animaciones de layout (LayoutTransition) interceptan los cambios en las posiciones de las Views secundarias y aplican una animación de transición. Cuando LayoutTransition está habilitado, onLayout primero establece las posiciones finales, luego LayoutTransition anima el movimiento desde la posición anterior a la nueva. Esto requiere una implementación correcta de onLayout con coordenadas finales adecuadas.
invalidate() activa solo la fase draw (redibujado), sin afectar measure y layout. Para activar onLayout, debe llamar a requestLayout(), que inicia el ciclo completo: measure → layout → draw. invalidate es más eficiente para actualizar la apariencia cuando las dimensiones y posiciones no cambian.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también