Row, Column y Box: arreglo, alineación y peso
Los tres contenedores que resuelven el noventa por ciento de las pantallas no son primitivas mágicas: son políticas de medida escritas con el mismo protocolo que puedes usar tú. Esta lección los estudia por dentro — el par arreglo y alineación como los dos ejes independientes de cualquier contenedor lineal, el algoritmo real de reparto del espacio sobrante que hay detrás del modificador de peso, y el modo en que un contenedor de apilamiento decide su tamaño y ancla a sus hijos — para que dejes de elegirlos por costumbre y empieces a predecir su resultado antes de ejecutarlo.
Un contenedor lineal parece un componente y es en realidad una política de medida de una veintena de líneas. Row y Column reparten un eje principal y alinean el secundario; Box no reparte nada y se limita a apilar. Con esos tres se construye casi cualquier pantalla, y precisamente por eso su comportamiento se aprende por imitación y se interioriza mal: se sabe qué combinación produce el resultado deseado sin saber por qué, y en cuanto aparece un caso ligeramente distinto —un peso dentro de un contenedor desplazable, dos hijos que deben compartir la altura del más alto, un texto que se niega a truncarse— la intuición prestada deja de funcionar. Lo que sigue es el modelo real, en los mismos términos de Constraints y Placeable de la lección anterior.
- Separar el arreglo del eje principal de la alineación del eje secundario y saber cuál gobierna qué.
- Reconstruir el algoritmo de reparto del espacio sobrante que implementa el modificador
weight. - Predecir qué ocurre con un peso cuando el eje principal no tiene techo finito.
- Explicar cómo un contenedor de apilamiento decide su tamaño y qué hijos participan en esa decisión.
Dos ejes, dos mecanismos distintos
Un contenedor lineal trata sus dos ejes de forma radicalmente asimétrica y ahí está la clave para no confundir nunca más los parámetros. En el eje principal —el horizontal para una fila, el vertical para una columna— los hijos se suceden, de modo que la pregunta pertinente es cómo se distribuye el espacio que sobra entre ellos: eso es el arreglo. En el eje secundario los hijos se superponen, cada uno con su propia medida, y la pregunta es dónde se sitúa cada uno dentro de la franja común: eso es la alineación.
De ahí se sigue una regla nemotécnica que elimina la mitad de las dudas de la API: el parámetro de arreglo siempre nombra el eje principal del contenedor y el de alineación siempre nombra el secundario. Una fila recibe un arreglo horizontal y una alineación vertical; una columna, exactamente lo contrario. No hay excepciones ni casos especiales, y cuando el compilador rechaza un valor suele ser porque se está intentando alinear en el eje que se arregla.
El arreglo no es un enumerado cerrado sino un objeto con su propia lógica de distribución, y merece la pena conocer los que resuelven casos que la gente suele resolver a mano. Los tres arreglos de separación reparten el sobrante entre los huecos con distintas reglas sobre los extremos. El arreglo de espaciado fijo introduce una separación constante entre elementos consecutivos y es casi siempre preferible a poner rellenos en cada hijo, porque no añade separación antes del primero ni después del último y sobrevive a que la lista quede vacía. Y existe una variante que combina separación fija con alineación del bloque resultante, útil cuando quieres elementos juntos pero centrados como grupo.
// El sobrante del eje principal se distribuye; el secundario se alinea.
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icono()
Text(titulo, modifier = Modifier.weight(1f))
Boton()
}
// Un hijo puede desviarse de la alineacion comun del contenedor.
Column(horizontalAlignment = Alignment.Start) {
Text(cabecera)
Text(pie, modifier = Modifier.align(Alignment.End))
}
Arreglo
Gobierna el eje principal y responde a una sola pregunta: qué hacer con el espacio que sobra después de medir a todos. Solo tiene sentido si sobra algo.
Alineación
Gobierna el eje secundario, donde los hijos se superponen. No reparte nada: sitúa cada hijo dentro de la franja común, y admite excepción por hijo.
Peso
Convierte a un hijo en destinatario del sobrante. Deja de elegir su tamaño en el eje principal: lo recibe impuesto como medida exacta.
Línea base
La tercera vía de alineación, tipográfica en lugar de geométrica. Alinea por la primera línea de texto de cada hijo y no por los bordes de su caja.
Existe una tercera forma de alinear que se olvida casi siempre y que resuelve el problema tipográfico que ninguna alineación geométrica resuelve: alinear por la línea base del texto en lugar de por los bordes de la caja. Dos textos de tamaños distintos centrados verticalmente se ven mal porque sus líneas base no coinciden; alineados por línea base se ven como una sola frase. El mecanismo subyacente son las líneas de alineación, un canal adicional por el que un hijo puede publicar posiciones notables de su interior para que el padre las use al colocar, y lo verás desde dentro cuando escribas tu propio contenedor.
El reparto del sobrante: cómo funciona weight de verdad
El modificador de peso se explica habitualmente diciendo que reparte el espacio proporcionalmente, lo cual es cierto y no basta para predecir su comportamiento. El algoritmo real ocurre en dos grupos dentro de una única pasada, respetando la prohibición de medir dos veces.
En el primer grupo, el contenedor mide a todos los hijos que no tienen peso. Les entrega el techo disponible en el eje principal, y va restando de un contador lo que cada uno consume. Estos hijos son medidos con libertad: eligen su tamaño natural. En el segundo grupo, lo que quede en el contador se divide entre la suma total de los pesos declarados, y a cada hijo con peso se le entrega su parte. La diferencia crucial es cómo se le entrega: no como un techo, sino como unos Constraints fijos en el eje principal. El hijo con peso no elige su tamaño, se le impone.
flowchart TD A[El contenedor recibe su techo en el eje principal] --> B[Mide primero los hijos sin peso] B --> C[Resta lo consumido y obtiene el sobrante] C --> D[Divide el sobrante entre la suma de pesos] D --> E[Da constraints fijos a cada hijo con peso] E --> F[Coloca todo segun el arreglo elegido] style B fill:#f9e2af,color:#11111b style E fill:#a6e3a1,color:#11111b
Ese detalle explica de golpe varios comportamientos que suelen atribuirse a fallos. Un texto largo dentro de un hijo con peso se trunca en lugar de empujar al resto, porque su anchura ya está decidida antes de que el texto opine. Un hijo con peso ocupa su cuota completa aunque su contenido sea diminuto, porque el mínimo impuesto es igual al máximo. Y si se quiere que un hijo con peso pueda quedarse por debajo de su cuota, existe un segundo parámetro del modificador que relaja el mínimo a cero: entonces recibe su parte como techo y no como obligación.
El reparto trabaja en píxeles enteros, así que hay un residuo que no divide de forma exacta. El contenedor no lo descarta: reparte los píxeles sobrantes uno a uno entre los primeros hijos con peso, de modo que la suma final coincide exactamente con el espacio disponible. Por eso dos hijos con peso idéntico pueden diferir en un píxel, y por eso una fila de columnas con pesos iguales no siempre produce columnas idénticas al píxel.
El algoritmo entero descansa sobre la existencia de un sobrante finito, y el sobrante es el techo del eje principal menos lo consumido. Dentro de un contenedor con desplazamiento en ese mismo eje, el techo es infinito por diseño: no hay sobrante que repartir porque el espacio disponible no está acotado. Una fila desplazable horizontalmente con hijos que declaran peso es, por tanto, una contradicción de términos, y el resultado va del hijo colapsado a la excepción en tiempo de ejecución según la versión de la biblioteca. La regla práctica es sencilla: peso y desplazamiento no comparten eje. Si necesitas ambos, el peso pertenece al eje que no se desplaza.
Un uso del peso que pasa desapercibido es aplicarlo a un separador vacío. Un hueco flexible con peso empuja a lo que viene después hasta el extremo contrario sin necesidad de arreglos especiales, y a diferencia de un arreglo de separación funciona aunque solo haya dos hijos y quieras exactamente ese comportamiento en ese punto y no en todos los huecos. Es la forma más barata de escribir una barra con contenido a la izquierda y acciones a la derecha.
Hay una interacción entre el peso y el arreglo que conviene tener clara porque parece contradictoria. Si algún hijo tiene peso, no queda sobrante: el reparto consume por definición todo el espacio disponible, y por tanto el arreglo elegido deja de tener efecto visible salvo en la separación que introduzca entre elementos. Un contenedor con un hijo con peso y un arreglo de centrado no centra nada, y esa combinación suele ser la huella de una duda no resuelta sobre qué se pretendía. La decisión sana es explícita: o se reparte el sobrante con pesos, o se distribuye con un arreglo, pero mezclar ambas cosas en el mismo eje solo confunde a quien lea el código después.
El orden de medida tiene además una consecuencia sobre la robustez que conviene anticipar. Como los hijos sin peso se miden primero y con el techo completo, un hijo sin peso que decida ocupar todo el espacio disponible dejará el contador a cero y los hijos con peso recibirán una cuota nula. No hay ningún error, no hay ninguna excepción: simplemente desaparecen. Ese es el diagnóstico correcto para el síntoma clásico de un elemento que no se ve dentro de una fila cuyos hermanos sí se ven, y casi siempre se arregla quitando la orden de llenar el espacio del hermano equivocado.
Box: apilar, anclar y decidir el tamaño
El contenedor de apilamiento no reparte ningún eje: coloca a todos sus hijos en el mismo espacio, uno encima de otro en el orden de declaración, y el último escrito queda visualmente arriba. Su alineación es bidimensional en lugar de por ejes, con nueve posiciones canónicas, y admite tanto una alineación común para todo el contenido como una alineación propia por hijo.
Su política de medida tiene dos sutilezas que conviene conocer. La primera es que relaja los mínimos que recibe antes de pasarlos a los hijos: aunque al contenedor se le exija un tamaño mínimo, los hijos reciben cero como mínimo y eligen libremente su tamaño natural, y solo después el contenedor se estira para cumplir con lo que le pedían. Existe un parámetro para desactivar esa relajación y propagar los mínimos hacia abajo, que es lo que se necesita cuando quieres que un único hijo llene exactamente el hueco que el padre te impuso.
La segunda es que no todos los hijos participan en la decisión del tamaño. Un hijo marcado para igualar el tamaño del contenedor queda deliberadamente fuera del cálculo: el contenedor mide primero a los demás, decide con ellos su tamaño y solo entonces mide a los marcados, con Constraints fijos iguales a esa medida ya fijada. Es la manera de tener un fondo, un borde o una superposición que se ajusten al contenido sin que ellos mismos inflen ese contenido, y no debe confundirse con el modificador que pide llenar el espacio disponible, que sí participa del cálculo y produce un contenedor tan grande como su techo.
Box(contentAlignment = Alignment.Center) {
// Este hijo decide el tamano del contenedor
Text(mensaje, modifier = Modifier.padding(24.dp))
// Este se adapta a la medida ya decidida y no la altera
Superficie(modifier = Modifier.matchParentSize())
// Y este se ancla a una esquina sin afectar a nada
Insignia(modifier = Modifier.align(Alignment.TopEnd))
}
Ese orden en dos tiempos es exactamente la misma maniobra que emplea el reparto por pesos: medir primero a quien puede decidir, fijar con esa información una medida, e imponerla después a quien depende de ella. Toda la biblioteca de contenedores está construida con esa figura, y reconocerla es lo que convierte tres comportamientos aparentemente arbitrarios en una sola idea. Cuando escribas el tuyo en la lección siguiente, esa será la primera herramienta que uses.
La implementación tiene un camino rápido para cero o un hijo que evita el recorrido y la lógica de alineación por hijo. Aun así, usar este contenedor solo para aplicar un relleno o un fondo añade un nodo al árbol sin motivo: esos efectos son modificadores y viven en la cadena del propio hijo. La regla es apilar cuando de verdad hay superposición, y modificar cuando lo que quieres es decorar.
Elegir con criterio en lugar de por costumbre
Con el modelo en la mano, la elección entre los tres deja de ser estilística. Si los hijos se suceden en un eje y el otro es secundario, es un contenedor lineal, y la única pregunta pendiente es si el reparto del eje principal lo decide un arreglo o unos pesos. Si los hijos ocupan el mismo espacio, es un apilamiento. Y si ninguna de las dos frases describe lo que quieres, no es que falte un parámetro: es que necesitas una política propia, que es la lección siguiente.
El error de estructura más común no es elegir mal sino anidar de más. Una fila dentro de una columna dentro de otra fila para conseguir una disposición que en realidad describe una sola regla produce un árbol con tres nodos donde bastaba uno, y sobre todo reparte la especificación en tres sitios. Cada nivel añadido es además una frontera donde los Constraints se transforman, y por tanto un lugar donde el resultado puede sorprender.
// Una barra tipica: contenido a la izquierda, acciones a la derecha,
// sin arreglos especiales y con un unico nodo contenedor.
Row(verticalAlignment = Alignment.CenterVertically) {
Text(titulo)
Spacer(Modifier.weight(1f)) // el hueco flexible empuja lo que sigue
AccionCompartir()
AccionMenu()
}
Queda un criterio de higiene que ahorra muchas revisiones. Un contenedor cuyo único propósito es aplicar un fondo, un relleno o un recorte no debería existir: esos efectos son modificadores y pertenecen a la cadena del hijo. Un contenedor existe cuando hay más de un hijo que coordinar o cuando la disposición de uno solo depende de un espacio que hay que negociar. Aplicar esa regla con constancia reduce la profundidad del árbol más que cualquier optimización posterior.
Lo más valioso de estos tres contenedores no es lo que resuelven sino que están escritos exactamente con el vocabulario que aprendiste en la lección anterior, sin ninguna capacidad privilegiada. Un contenedor lineal no tiene acceso a un canal secreto para saber cuánto miden sus hijos antes de medirlos; hace lo mismo que harías tú: mide primero a los que puede medir con libertad, contabiliza lo consumido y con lo que queda impone Constraints fijos a los demás. El contenedor de apilamiento no tiene un truco para que un hijo iguale su tamaño; aplaza la medida de ese hijo hasta después de haber decidido el suyo, que es la única forma legal de conseguirlo dentro de la pasada única. Verlos así cambia la relación con la biblioteca en dos direcciones. Hacia dentro, porque cada comportamiento raro deja de ser una peculiaridad memorizable y se convierte en la consecuencia deducible de una política concreta: el texto que se trunca dentro de un peso, el hijo que no se encoge, el peso que se evapora al meterlo en un desplazamiento. Hacia fuera, porque te enseña el catálogo de maniobras disponibles cuando tengas que escribir el tuyo: dividir los hijos en grupos y medirlos en órdenes distintos, imponer medidas en lugar de ofrecer techos, dejar hijos fuera del cálculo del tamaño, publicar posiciones notables para que el padre alinee por ellas. No hay más magia que esa. Cuando en la próxima lección construyas un contenedor propio, no estarás bajando a un nivel inferior del sistema: estarás escribiendo en el mismo nivel en el que están escritos estos tres.
- Escribe una fila con tres hijos de pesos uno, dos y uno, y un cuarto hijo sin peso de anchura fija. Calcula sobre el papel la anchura resultante de cada uno para un contenedor de trescientos veinte píxeles y comprueba después el residuo del reparto entero.
- Repite el ejercicio poniendo un texto largo en el hijo de peso dos y explica por qué el texto se trunca en lugar de ensanchar su hueco.
- Cambia el segundo parámetro del modificador de peso para permitir que un hijo quede por debajo de su cuota y describe qué
Constraintsrecibe en cada caso. - Mete la misma fila dentro de un contenedor con desplazamiento horizontal y documenta con precisión qué ocurre y por qué.
- Construye un contenedor de apilamiento con un contenido de tamaño variable y un fondo que deba ajustarse a él. Resuélvelo primero con el modificador de igualar tamaño y después con el de llenar el espacio, y explica la diferencia observada en el tamaño final.