onMeasure() es un método protegido de la clase android.view.View que el sistema Android llama para determinar el tamaño de una View. El sistema pasa dos objetos MeasureSpec al método, cada uno contiene un modo de medición (EXACTLY, AT_MOST o UNSPECIFIED) y un tamaño sugerido por el contenedor padre. Según la Documentación para Desarrolladores de Android (2026), sobrescribir onMeasure con un manejo correcto de MeasureSpec es un paso obligatorio para todas las Views y ViewGroups personalizadas que requieren un control preciso del tamaño.
Puntos clave
onMeasure(int widthMeasureSpec, int heightMeasureSpec) es un método de la clase View que el sistema Android llama para determinar el ancho y alto de una vista. El desarrollador sobrescribe este método para especificar qué tamaño debe tener la View según las restricciones pasadas en MeasureSpec. Sin una sobrescritura correcta de onMeasure, una View personalizada puede mostrarse incorrectamente o no aparecer en absoluto.
El sistema llama a onMeasure durante la fase measure del ciclo de vida de la View, que precede a las fases de layout (onLayout) y draw (onDraw). Si una View no sobrescribe onMeasure, se usa la implementación de la superclase, que establece tamaños predeterminados basados en el background drawable o layout_params. Llamar a super.onMeasure(widthMeasureSpec, heightMeasureSpec) solo funciona para subclases estándar de View, como TextView o ImageView.
Un requisito clave de onMeasure es que la llamada a setMeasuredDimension(int, int) debe estar presente al final del método. Si falta esta llamada, el sistema lanza una IllegalStateException indicando que la View no fijó las dimensiones medidas. Las dimensiones finales están disponibles a través de los getters getMeasuredWidth() y getMeasuredHeight() después de que la fase measure se completa.
MeasureSpec es un entero de 32 bits donde los 2 bits superiores codifican el modo de medición y los 30 bits inferiores codifican el tamaño. El modo determina qué tan libre está la View para elegir su propio tamaño. Android proporciona tres modos: EXACTLY, AT_MOST y UNSPECIFIED. Cada modo dicta una lógica de procesamiento diferente en onMeasure.
| Modo MeasureSpec | Valor | Comportamiento |
|---|---|---|
| EXACTLY | El padre especificó un tamaño exacto | La View debe ajustarse exactamente al tamaño dado si no quiere salirse de los límites |
| AT_MOST | El padre estableció un tamaño máximo | La View puede elegir cualquier tamaño de 0 al máximo dado |
| UNSPECIFIED | El padre no impone restricciones | La View puede elegir cualquier tamaño deseado sin límite superior |
Para extraer el modo y el tamaño de MeasureSpec se usan los métodos estáticos de la clase MeasureSpec: MeasureSpec.getMode(int) devuelve uno de los tres modos (EXACTLY, AT_MOST, UNSPECIFIED), y MeasureSpec.getSize(int) devuelve el tamaño numérico en píxeles. Para crear un MeasureSpec personalizado se usa MeasureSpec.makeMeasureSpec(int size, int mode). Estos tres métodos cubren todos los escenarios de trabajo con tamaños en onMeasure.
El patrón estándar para manejar MeasureSpec: si el modo es EXACTLY, usa el tamaño dado como final; si es AT_MOST, elige el mínimo entre el tamaño deseado (el contenido de la View) y el máximo dado; si es UNSPECIFIED, usa el tamaño deseado de la View sin restricciones. Este patrón garantiza un comportamiento correcto bajo cualquier restricción del padre.
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)
}
Para simplificar la lógica estándar, Android proporciona el método resolveSizeAndState, que toma el tamaño deseado, MeasureSpec y devuelve el tamaño final con el modo correcto. Este método implementa el patrón descrito anteriormente en una sola línea de código. También está disponible la función resolveSize(int size, int measureSpec), que devuelve un tamaño limpio sin bits de estado.
Android utiliza un algoritmo de medición de dos pasos que garantiza que cada View en la jerarquía reciba dimensiones correctas considerando las restricciones del padre y las preferencias de los hijos. En el primer paso, el padre pasa MeasureSpec con restricciones a las Views hijas, y las Views hijas calculan sus tamaños deseados. En el segundo paso, el padre toma la decisión final sobre los tamaños.
Para ViewGroup, el proceso de medición es más complejo: el padre primero debe medir a todos sus hijos, luego determinar su propio tamaño basado en los tamaños de ellos. Llamar a measureChildren(int widthMeasureSpec, int heightMeasureSpec) itera a través de todas las Views hijas y llama a measure(child, childWidthSpec, childHeightSpec) para cada una. Después de medir a todos los hijos, la ViewGroup llama a setMeasuredDimension con sus propias dimensiones.
Un matiz importante: el método measure (público, final) no se puede sobrescribir — en su lugar se sobrescribe onMeasure. Esto garantiza que el sistema pueda realizar tareas de mantenimiento antes y después de onMeasure, como verificar cambios de tamaño y calcular el área sucia para el dibujo posterior. Si una View tiene dimensiones fijas, puede que no sea necesario sobrescribir onMeasure.
MeasureSpec incluye no solo el tamaño y el modo, sino también bits de estado, accesibles a través de MeasureSpec.getMode(). Después de llamar a setMeasuredDimension, el estado pasa a formar parte de las dimensiones medidas de la View y se puede verificar mediante getMeasuredState(). Esto se usa en ScrollView y otros contenedores desplazables para pasar correctamente las restricciones a los hijos.
Examinemos un ejemplo práctico de creación de una View personalizada con onMeasure sobrescrito para visualización cuadrada. La clase SquareView extiende View y garantiza que el ancho y el alto sean siempre iguales, independientemente del MeasureSpec pasado. En onMeasure se determina el lado mínimo y se establece el tamaño cuadrado.
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 requiere una lógica onMeasure más compleja porque primero se deben medir los hijos, luego se determina el tamaño de la propia ViewGroup. El ejemplo CascadeLayout distribuye los hijos en cascada con un desplazamiento. Después de medir todos los hijos mediante measureChildWithMargins, se calculan el ancho y alto totales.
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
}
}
}
Falta de llamada a setMeasuredDimension es el error más común. Si un desarrollador sobrescribe onMeasure pero no llama a setMeasuredDimension, la aplicación falla con IllegalStateException. Esto ocurre especialmente cuando el método tiene ramas condicionales y en una de ellas falta la llamada. Cada rama de código en onMeasure debe terminar con una llamada a setMeasuredDimension.
Ignorar el modo AT_MOST es el segundo error más frecuente. Si una View en modo AT_MOST siempre usa el tamaño pasado en lugar de calcular según el contenido, el contenedor padre no puede distribuir correctamente el espacio. Por ejemplo, un TextView en AT_MOST debe calcular el ancho del texto y usar el mínimo entre el ancho deseado y el pasado. Ignorar AT_MOST hace que la View ocupe todo el espacio disponible incluso con contenido pequeño.
Crear objetos dentro de onMeasure es un error clásico de rendimiento. Dado que onMeasure puede llamarse múltiples veces (en cada solicitud de layout), crear objetos (Paint, Rect, String) dentro de este método ensucia la memoria y provoca recolección de basura. Todos los objetos deben crearse una vez en el constructor de la View, y solo la lógica de cálculo de tamaños debe ejecutarse en onMeasure. La misma regla aplica a onDraw y onLayout.
measureChildWithMargins es un método protegido de ViewGroup que mide una sola View hija considerando sus MarginLayoutParams. El método acepta el MeasureSpec del padre y los desplazamientos acumulados de ancho y alto. Ajusta automáticamente el MeasureSpec para la View hija restando los paddings del padre y los margins del hijo, luego pasa el MeasureSpec ajustado a child.measure().
Para una lógica de medición avanzada, una ViewGroup puede sobrescribir measureChild(View child, int parentWidthSpec, int parentHeightSpec) o trabajar directamente con MeasureSpec para cada hijo. Por ejemplo, LinearLayout en onMeasure itera a través de todas las Views hijas, mide cada una considerando su layout_weight y distribuye el espacio restante proporcionalmente. Este enfoque permite implementar algoritmos de diseño arbitrarios.
El almacenamiento en caché de los resultados de medición mediante el mecanismo de caché de medida está disponible a través de la bandera setMeasureWithLargestChildEnabled en ciertas ViewGroups. Sin embargo, en la mayoría de los casos onMeasure se llama nuevamente ante cualquier cambio de layout y el caché no se aplica. En ViewGroups personalizadas, se recomienda minimizar los cálculos en onMeasure en lugar de depender del caché.
Preguntas frecuentes
Sí, si la View personalizada hereda directamente de la clase View. Si hereda de TextView, ImageView o Button con sus tamaños estándar, onMeasure puede dejarse sin cambios. Para ViewGroup, sobrescribir onMeasure siempre es necesario — de lo contrario, los hijos no se medirán correctamente.
El sistema Android lanza IllegalStateException con el mensaje “The View did not call setMeasuredDimension”. Esta excepción ocurre en el método measure() después de que onMeasure se completa, si las dimensiones finales quedaron en cero. La excepción bloquea la aplicación si no se maneja mediante try-catch.
getMeasuredWidth() devuelve el tamaño establecido en onMeasure (fase de medición). getWidth() devuelve el tamaño real que la View recibió en onLayout después de todos los ajustes de posicionamiento. Para la mayoría de las Views estos valores coinciden, pero en ViewGroups personalizadas pueden diferir.
No, onMeasure está destinado exclusivamente al cálculo de tamaños. Cambiar el estado, iniciar animaciones, hacer networking o actualizar datos en este método viola la arquitectura de Android y puede provocar llamadas recursivas a measure, ya que los cambios de estado pueden desencadenar requestLayout.
ConstraintLayout gestiona la medición de los hijos de forma independiente según las restricciones definidas. Si una View personalizada dentro de ConstraintLayout sobrescribe onMeasure, debe manejar correctamente el MeasureSpec pasado desde ConstraintLayout, de lo contrario las restricciones podrían no funcionar. ConstraintLayout utiliza un algoritmo de dos pasos con su propio WidgetContainer para el cálculo.
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