onLayout(): qué es, algoritmo de diseño y parámetros del método

Autor: IT Sectr Publicado: 2026-07-22 Tiempo de lectura: 9 min

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) — método de ViewGroup que determina las posiciones de los elementos secundarios en coordenadas del padre
  • child.layout(l, t, r, b) — llamada para cada View secundaria, estableciendo sus límites finales
  • La fase layout sigue a la fase measure y precede a la fase draw en el ciclo de vida de View
  • getWidth() y getHeight() están disponibles solo después de ejecutar onLayout, a diferencia de getMeasuredWidth que está disponible después de onMeasure
  • requestLayout() — método que inicia una llamada repetida de onMeasure y onLayout cuando cambian los datos que afectan el posicionamiento

¿Qué es onLayout()?

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.

Flujo de la Fase Layout

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.

Parámetros de onLayout: l, t, r, b

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ámetroDescripciónUso Típico
l (left)Coordenada del borde izquierdo de la ViewGroup en el padrePunto inicial en el eje X para elementos secundarios
t (top)Coordenada del borde superior de la ViewGroup en el padrePunto inicial en el eje Y para elementos secundarios
r (right)Coordenada del borde derecho de la ViewGroup en el padreLímite superior de ancho, r - l = getWidth()
b (bottom)Coordenada del borde inferior de la ViewGroup en el padreLímite superior de altura, b - t = getHeight()

Cálculo de Coordenadas para Elementos Secundarios

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

Ejemplo de ViewGroup Personalizada con onLayout en Kotlin

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.

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

Diferencias entre onLayout y onMeasure

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.

Errores Comunes al Trabajar con onLayout

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

¿Es posible no sobrescribir onLayout en ViewGroup?

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.

¿Cuál es la diferencia entre layout y 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.

¿Puede onLayout llamar a requestLayout?

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.

¿Cómo funciona onLayout con animaciones?

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.

¿Por qué no se llama a onLayout después de invalidate?

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

  • onLayout() — método de ViewGroup que determina las posiciones finales de las Views secundarias después de completar la fase de medición
  • child.layout(l, t, r, b) — el mecanismo principal para establecer coordenadas para cada elemento secundario
  • Parámetros l, t, r, b — coordenadas de los bordes de la ViewGroup en el sistema padre, ancho = r - l, alto = b - t
  • La fase layout se propaga recursivamente desde la View raíz a los elementos secundarios, llamando a onLayout en cada ViewGroup
  • requestLayout() activa un ciclo completo de recálculo de dimensiones y posiciones, a diferencia de invalidate que solo activa el redibujado
  • Tener en cuenta el padding en onLayout es obligatorio — las coordenadas iniciales de los hijos deben incluir el paddingLeft y paddingTop del padre

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.

Discutir el proyecto

Lea también