Intrínsecos: preguntar cuánto necesitarías antes de medir
Los intrínsecos son la primera de las dos escapatorias reguladas a la pasada única: un canal por el que un padre puede preguntar a un hijo cuánto espacio necesitaría sin llegar a medirlo, y usar la respuesta para decidir con qué constraints medirlo de verdad. Esta lección precisa el significado exacto de las cuatro consultas, muestra los dos problemas clásicos que resuelven, desmonta su coste real explicando qué ocurre cuando no implementas la consulta en tu propio contenedor, y enumera los componentes que directamente no la soportan y las alternativas que casi siempre salen más baratas.
Hay un problema que el protocolo de la pasada única no sabe resolver por sí solo: cuando el tamaño que debe recibir un hijo depende de lo que necesitan sus hermanos. Un separador vertical que deba llegar exactamente hasta donde llega el más alto de dos textos; dos botones que deban compartir la anchura del que tenga la etiqueta más larga. En ambos casos el padre necesita información sobre el contenido antes de imponer medidas, y volver a medir está prohibido. Los intrínsecos son la salida reglamentada: un canal aparte por el que se pregunta cuánto necesitarías si tuvieras esta otra dimensión, sin que la respuesta cuente como una medida. La pregunta se puede formular; lo que hay que entender antes de formularla es lo que cuesta.
- Enunciar con precisión qué devuelve cada una de las cuatro consultas intrínsecas.
- Resolver con ellas los dos problemas canónicos de altura y anchura compartidas.
- Explicar por qué una consulta cuesta una travesía completa del subárbol y cómo se multiplica al anidarse.
- Reconocer los componentes que no las soportan y elegir la alternativa correcta en cada caso.
Las cuatro preguntas y lo que significan de verdad
Un medible expone cuatro consultas, dos por eje. Cada una recibe la otra dimensión como parámetro, porque la respuesta depende de ella: preguntar cuánta altura necesita un texto solo tiene sentido si se dice con qué anchura. La confusión habitual está en el par mínimo y máximo, que no significan lo que sugiere el lenguaje corriente.
La consulta de anchura mínima devuelve la menor anchura con la que el contenido todavía puede representarse correctamente, no la menor con la que cabría de cualquier manera. Para un texto, esa cifra es la anchura de la palabra más larga: por debajo de eso habría que partir palabras, que es lo que se considera incorrecto. La consulta de anchura máxima devuelve la menor anchura a partir de la cual seguir ensanchando ya no cambia nada; para un texto, la anchura de todo el contenido en una sola línea. No es un techo sino un punto de saturación, y esa es la lectura que evita la mitad de los errores de interpretación.
Nótese que ninguna de las dos habla de lo que el elemento va a medir, sino de lo que necesitaría. Es una cifra hipotética, y por eso puede formularse sin consumir la única medida disponible. Un texto consultado por su anchura de saturación responde con la anchura de todo su contenido en una línea aunque después se le vaya a imponer la mitad y acabe ocupando dos; la respuesta no le compromete a nada.
Las dos consultas de altura son simétricas con la salvedad de que, para contenido que se ajusta al ancho disponible, ambas suelen coincidir: dada una anchura concreta, un texto tiene una altura y solo una. Por eso la consulta de altura mínima es la que se usa casi siempre, y la de altura máxima aparece sobre todo en contenedores que reparten espacio.
Anchura mínima
Lo más estrecho que puede ser sin romperse. En texto, la palabra más larga. En un contenedor lineal horizontal, la suma de los mínimos de sus hijos.
Anchura máxima
El punto de saturación: más ancho no mejora nada. En texto, todo en una línea. Es lo que iguala dos botones a la etiqueta más larga.
Altura mínima
La altura necesaria dada una anchura. Es la consulta que hace que un separador alcance exactamente la altura del hermano más alto.
Altura máxima
Rara vez distinta de la anterior salvo en contenedores que reparten. Útil cuando un hijo puede estirarse y quieres su techo natural.
En la práctica no se invocan a mano: se usan los modificadores de tamaño que aceptan un valor intrínseco en lugar de una medida concreta. Lo que hace ese modificador es formular la consulta al subárbol que envuelve y convertir la respuesta en Constraints fijos para la medida real. Con eso, los dos problemas canónicos se resuelven en una línea cada uno.
// Un separador que llega exactamente hasta el hermano mas alto
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
Text(izquierda, Modifier.weight(1f))
VerticalDivider(Modifier.fillMaxHeight())
Text(derecha, Modifier.weight(1f))
}
// Dos botones con la anchura del que tenga la etiqueta mas larga
Column(modifier = Modifier.width(IntrinsicSize.Max)) {
Button(onClick = aceptar, modifier = Modifier.fillMaxWidth()) { Text(si) }
Button(onClick = cancelar, modifier = Modifier.fillMaxWidth()) { Text(quizasNo) }
}
Conviene fijarse en lo que ocurre en el primer ejemplo, porque explica el mecanismo entero. El modificador pregunta a la fila su altura mínima; la fila responde consultando la de cada hijo y quedándose con la mayor; esa cifra se convierte en altura fija de la fila; y ya con la altura fijada, el separador que pide llenar la altura disponible recibe un valor concreto en lugar de la altura infinita que recibiría dentro de una fila que se ajusta al contenido. Sin la consulta previa, pedir llenar el espacio disponible en un contenedor que se ajusta a su contenido es una circularidad, y el resultado es un separador de altura cero.
Lo que cuesta preguntar
Una consulta intrínseca no es una segunda medida y por eso no infringe la regla de la lección primera. Tampoco es gratis, y el motivo está en cómo se responde. Cuando un contenedor de la biblioteca recibe la consulta, la propaga a sus hijos con la lógica que corresponda a su política y compone las respuestas; cada hijo hace lo mismo con los suyos. Una consulta, por tanto, es una travesía completa del subárbol, con el mismo orden de coste que una medida aunque sin crear Placeable.
El caso interesante es el de un contenedor propio que no implementa las consultas. La implementación por defecto no se rinde: ejecuta tu propia política de medida sobre un conjunto de hijos simulados que, en lugar de medirse, devuelven sus propios valores intrínsecos. Es decir, tu lambda de medida se ejecuta entera una vez más por cada consulta. Eso tiene dos consecuencias que sorprenden. La primera es que el coste no es una estimación barata sino una ejecución completa de tu política. La segunda es que el resultado puede ser sorprendentemente bueno o directamente absurdo según cómo esté escrita esa política, porque nadie ha verificado que tenga sentido ejecutarla con hijos falsos.
flowchart TD A[El modificador pide un tamano intrinseco] --> B[La consulta baja por todo el subarbol] B --> C[Cada contenedor la propaga a sus hijos] C --> D[Las respuestas suben y se componen] D --> E[El resultado se convierte en constraints fijos] E --> F[Empieza la medida real, que baja otra vez] style B fill:#f9e2af,color:#11111b style F fill:#89b4fa,color:#11111b
De ahí sale una recomendación que parece de estilo y es de coste: si tu contenedor va a vivir en una biblioteca compartida o dentro de un componente reutilizable, implementa las consultas explícitamente aunque creas que nadie las va a usar. Quien las use lo hará desde fuera, sin ver tu política, y la diferencia entre una respuesta calculada en dos líneas y una obtenida ejecutando toda la política puede ser de un orden de magnitud en un subárbol denso.
La multiplicación aparece al anidar. Si un contenedor consultado responde consultando a su vez a los suyos y alguno de esos hijos usa a su vez un tamaño intrínseco, el número de travesías crece con el producto de los niveles implicados, no con su suma. Es una versión atenuada del mismo problema exponencial que la pasada única vino a resolver, con la diferencia de que aquí es explícito y voluntario: solo ocurre donde alguien escribió el modificador. Por eso el consejo operativo no es evitarlos, sino no anidarlos y no dejarlos caer en sitios que se ejecutan muchas veces.
Todo lo construido sobre subcomposición —las listas perezosas, el contenedor que expone sus propios Constraints, algunas barras de pestañas— no puede contestar a una consulta intrínseca, y no falla en silencio: lanza una excepción explícita diciendo que no está soportado. La razón es de fondo y se entenderá del todo en la última lección: esos componentes ni siquiera saben qué hijos tienen hasta que reciben unos Constraints concretos, así que no hay a quién preguntar. Envolver una lista perezosa en un modificador de tamaño intrínseco es, por tanto, un error de categoría y no un caso no implementado.
Implementarlos en un contenedor propio
Si escribes un contenedor y sabes que alguien va a consultarlo, implementar las cuatro consultas a mano evita el coste de ejecutar la política completa y, sobre todo, garantiza que la respuesta sea la correcta. Para eso hay que usar la forma de la primitiva que recibe una política de medida como objeto en lugar de como lambda, y sobrescribir los métodos correspondientes.
private val politicaFlujo = object : MeasurePolicy {
override fun MeasureScope.measure(
medibles: List<Measurable>,
constraints: Constraints,
): MeasureResult = TODO("la politica de la leccion anterior")
// Lo mas estrecho sin romper nada: el hijo mas ancho en su minimo.
override fun IntrinsicMeasureScope.minIntrinsicWidth(
medibles: List<IntrinsicMeasurable>,
alturaDisponible: Int,
): Int = medibles.maxOfOrNull { it.minIntrinsicWidth(alturaDisponible) } ?: 0
// Saturacion: todo en una sola fila.
override fun IntrinsicMeasureScope.maxIntrinsicWidth(
medibles: List<IntrinsicMeasurable>,
alturaDisponible: Int,
): Int = medibles.sumOf { it.maxIntrinsicWidth(alturaDisponible) }
}
El ejercicio de escribirlas obliga a algo saludable: definir qué significan para tu contenedor. En el de flujo de la lección anterior, la anchura mínima es la del hijo más ancho —por debajo de eso ninguna fila podría formarse— y la anchura de saturación es la suma de todos, porque a partir de ahí todo cabe en una línea y ensanchar no cambia nada. Esas dos frases son la especificación del componente en el eje horizontal, y responderlas mal produce disposiciones que fallan solo cuando alguien las consulta desde fuera, que es el tipo de fallo más difícil de rastrear.
Muchos usos de intrínsecos desaparecen al reformular el problema como un contenedor propio. Si necesitas que un elemento adopte la altura del hermano más alto, un contenedor propio puede medir primero a los hermanos ordinarios, quedarse con el máximo y medir después al elemento dependiente con Constraints fijos: una sola travesía, ninguna consulta y ninguna suposición. Es exactamente la maniobra con la que un contenedor de apilamiento resuelve el hijo que iguala su tamaño. La consulta intrínseca es la herramienta cuando el elemento dependiente no está bajo tu control; cuando lo está, reordenar la medida es más barato y más preciso.
Cuándo se justifican
La respuesta corta es que se justifican cuando el subárbol consultado es pequeño y estable, la alternativa exigiría reescribir un contenedor completo y el punto donde se colocan no se ejecuta muchas veces por fotograma. Los dos ejemplos canónicos del principio cumplen las tres condiciones: dos textos y un separador, un par de botones, en pantallas donde eso se mide una vez por composición.
Hay un tercer patrón, menos citado, en el que resultan especialmente adecuados: cuando el elemento dependiente es un componente ajeno cuya política no puedes reescribir. Un separador de una biblioteca de diseño, un componente de terceros o cualquier cosa que solo puedas configurar desde fuera con modificadores. En ese escenario no existe la alternativa de reordenar la medida, porque reordenarla exigiría ser el contenedor, y la consulta intrínseca es la única herramienta disponible sin envolver el componente en una capa propia.
La respuesta larga exige mirar el contexto de ejecución. Dentro del elemento de una lista perezosa, un modificador de tamaño intrínseco no se ejecuta una vez sino una por elemento visible y en cada fotograma en que la lista se remida, y ese factor multiplicador convierte un coste despreciable en un coste medible. Lo mismo ocurre dentro de un subárbol que se anima: una animación de tamaño provoca una medida por fotograma, y cada una arrastra su consulta previa.
Conviene también desarmar una intuición errónea muy extendida: que un tamaño intrínseco es una forma de ajustarse al contenido. No lo es. Ajustarse al contenido es el comportamiento por defecto de casi todo, y no cuesta nada porque sale del protocolo ordinario. Un tamaño intrínseco solo hace falta cuando alguien necesita conocer esa cifra antes de medir para imponérsela a otro, y si en tu caso nadie la necesita antes, el modificador está pagando una travesía a cambio del mismo resultado que habrías obtenido sin escribirlo.
Hay además un criterio de diseño que precede al de rendimiento. Una consulta intrínseca introduce una dependencia entre hermanos que no aparece en ningún sitio del código salvo en ese modificador, y que se rompe en silencio si alguien inserta un componente que no la soporte en medio de la jerarquía. Un contenedor propio con una política explícita hace visible esa dependencia y falla de forma comprensible cuando alguien la viola. Entre dos soluciones de coste similar, la que documenta su propia invariante siempre envejece mejor.
La forma más fértil de entender los intrínsecos es dejar de verlos como una medida barata y verlos como una negociación previa al contrato. La medida real es un acto irrevocable: el padre impone unos Constraints, el hijo devuelve un tamaño y ninguno de los dos puede volver atrás. Esa irrevocabilidad es justamente lo que garantiza el coste lineal, y también lo que impide resolver cualquier problema donde el tamaño de uno dependa de las necesidades de otro. El canal intrínseco abre una conversación anterior, deliberadamente asimétrica: el padre puede preguntar cuánto necesitarías si tuvieras tanto, y el hijo responde con una cifra que no le compromete a nada porque todavía no ha aceptado ningún contrato. Con esa información el padre firma una sola vez, y la pasada única sigue intacta. Entendido así, aparecen a la vez la potencia y el precio. La potencia es que cualquier dependencia entre hermanos se vuelve expresable sin romper la regla. El precio es que la negociación recorre el mismo terreno que el contrato: preguntar cuesta aproximadamente lo mismo que medir, y en un árbol donde varios niveles negocian, las conversaciones se cruzan y se multiplican. De ahí sale el criterio operativo que sustituye a cualquier regla memorizada: usa un intrínseco cuando la negociación sea corta y ocurra una vez, y reescribe el contenedor cuando la conversación sea larga o se repita muchas veces. Y cuando descubras que el interlocutor ni siquiera sabe qué hijos tendrá hasta conocer sus Constraints, no habrá negociación posible: ese es el terreno de la última lección.
- Construye la fila con separador y dos textos usando altura intrínseca mínima. Quita el modificador y explica por qué el separador desaparece en lugar de quedarse corto.
- Añade una traza al principio de la política de medida de tu contenedor propio y envuélvelo en un modificador de tamaño intrínseco. Cuenta cuántas veces se ejecuta la política por composición y explica el número.
- Implementa a mano las cuatro consultas de tu contenedor de flujo y repite la medición anterior. Compara.
- Anida dos niveles de tamaños intrínsecos y describe cómo crece el número de travesías al añadir un tercero.
- Envuelve una lista perezosa en un modificador de altura intrínseca, lee el mensaje de la excepción y reformula el diseño sin ella.