Grids perezosos: celdas, spans y escalonado
Una rejilla perezosa conserva el modelo de composición bajo demanda de las listas y le añade una decisión nueva: cómo repartir el eje transversal. Esta lección estudia las estrategias de celda fija, adaptativa y de tamaño fijo, y por qué la adaptativa es la pieza clave de una interfaz que debe funcionar en móvil, tablet y plegable; explica el sistema de spans y el ámbito que informa de cuántas columnas quedan; describe la rejilla escalonada, su algoritmo de carril más corto y las consecuencias que tiene sobre la estimación del scroll; y fija el criterio para elegir entre rejilla, lista con filas y layout perezoso a medida.
Pasar de una lista a una rejilla parece un cambio menor de presentación y en realidad introduce un grado de libertad nuevo que hay que decidir explícitamente: cuántas celdas caben a lo ancho. En una lista esa pregunta no existe porque la respuesta es siempre uno, y por eso la lista puede componer estrictamente lo que se ve. En una rejilla la respuesta depende del ancho disponible, que a su vez depende del dispositivo, de la orientación, del tamaño de la ventana en modo multiventana y del ángulo de un plegable. La rejilla perezosa no resuelve esa decisión por ti: te da tres estrategias con propiedades distintas y te obliga a elegir. Y cuando además los elementos tienen alturas dispares, aparece la variante escalonada, que abandona la cuadrícula regular a cambio de un compromiso que conviene entender antes de adoptarlo.
- Distinguir las estrategias de celda fija, adaptativa y de tamaño fijo, y sus consecuencias.
- Declarar spans variables para que ciertos elementos ocupen la línea completa.
- Entender el algoritmo de la rejilla escalonada y su efecto sobre la estimación del scroll.
- Elegir con criterio entre rejilla, lista con filas y layout perezoso a medida.
Las estrategias de celda
La rejilla vertical recibe un objeto que describe cómo repartir el ancho disponible entre columnas. Hay tres variantes de fábrica y la diferencia entre ellas no es de sintaxis sino de a quién le corresponde la decisión.
La estrategia fija declara un número exacto de columnas y reparte el ancho a partes iguales. Es predecible y suele ser lo que se quiere en un diseño cerrado, pero traslada al programador la responsabilidad de acertar en todos los tamaños de pantalla, lo que en la práctica significa consultar el tamaño de ventana y elegir el número a mano.
La estrategia adaptativa invierte el planteamiento: en lugar de decir cuántas columnas quieres, declaras el ancho mínimo aceptable para una celda, y el sistema calcula cuántas caben y reparte el sobrante entre ellas. Un teléfono en vertical dará dos columnas, el mismo teléfono en horizontal dará cuatro, y una tablet dará seis, sin una sola línea de lógica condicional. Es la opción por defecto razonable en cualquier interfaz que aspire a ser adaptativa.
La estrategia de tamaño fijo mantiene el ancho de celda exactamente en el valor indicado y deja libre el espacio sobrante, que se distribuye según la disposición horizontal declarada. Sirve cuando el contenido tiene un tamaño intrínseco que no debe deformarse, como miniaturas de un tamaño concreto.
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 148.dp),
contentPadding = PaddingValues(12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
items(fotos, key = { it.id }) { foto -> Miniatura(foto) }
}
Hay un detalle del reparto adaptativo que conviene entender para no llevarse sorpresas: el ancho mínimo declarado es un suelo, no un objetivo. El sistema calcula cuántas celdas de ese ancho caben, y el espacio sobrante no se deja libre sino que se reparte entre las columnas resultantes, de modo que las celdas reales siempre serán iguales o más anchas que el mínimo, nunca más estrechas. La consecuencia práctica es que el mínimo debe elegirse pensando en la legibilidad —el ancho por debajo del cual la tarjeta deja de funcionar— y no en el tamaño que uno quisiera ver, porque el tamaño real lo decidirá la pantalla.
Nada impide además escribir una estrategia propia implementando la interfaz que calcula los tamaños del eje transversal a partir del espacio disponible y la separación. Es la vía para políticas que ninguna de las tres describe, como una primera columna más ancha que las demás o un número de columnas que dependa de una preferencia del usuario. El contrato es corto: recibir el espacio y devolver la lista de anchos, cuya suma debe encajar en lo recibido.
Todo lo aprendido en las lecciones anteriores sigue vigente sin excepción: el bloque de contenido es un constructor de intervalos que se ejecuta con antelación, las claves siguen siendo obligatorias para la corrección del estado, el tipo de contenido sigue gobernando la reutilización, y el estado de la rejilla ofrece la misma información de layout con la misma advertencia sobre su frecuencia. La rejilla no es un componente nuevo: es la misma maquinaria con una política de colocación distinta.
Existe la variante horizontal, que declara filas en lugar de columnas y desplaza en el otro eje. Se usa mucho menos y casi siempre para carruseles de dos o tres filas dentro de una pantalla vertical. Ese anidamiento es legal porque los ejes son distintos, pero recuerda que cada carrusel es una rejilla completa con su propio estado, su propia reserva de reutilización y su propio coste de anticipación.
Spans: romper la retícula sin romper la rejilla
Con esto ya se puede formular la regla de decisión completa. Si el diseño exige un número concreto de columnas y la pantalla es de tamaño conocido, celdas fijas. Si el diseño exige un tamaño mínimo de celda y la pantalla puede ser cualquiera, celdas adaptativas. Si el contenido tiene un tamaño propio que no debe deformarse, celdas de tamaño fijo. Y si ninguna de las tres describe la política, una estrategia propia antes que una cadena de condicionales sobre el ancho.
Una rejilla real casi nunca es homogénea: tiene cabeceras de sección, banners, mensajes de estado vacío o elementos destacados que deben ocupar todo el ancho. Para eso existe el sistema de spans, que permite declarar cuántas columnas ocupa cada elemento.
El span se declara como una función del índice, evaluada dentro de un ámbito que informa de dos magnitudes muy útiles: el número máximo de columnas de una línea completa y el número de columnas que quedan libres en la línea actual. La primera sirve para las cabeceras a ancho completo; la segunda permite lógicas de relleno más finas, como hacer que un elemento se estire para no dejar un hueco al final de la línea.
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 148.dp)) {
item(span = { GridItemSpan(maxLineSpan) }) { CabeceraSeccion("Recientes") }
items(fotos, key = { it.id }) { foto -> Miniatura(foto) }
item(span = { GridItemSpan(maxLineSpan) }) { PieDeSeccion() }
}
La segunda magnitud, la de columnas libres en la línea actual, habilita un patrón que se ve poco y resuelve bien el problema estético del hueco final: hacer que el último elemento de una sección se estire para ocupar exactamente lo que quede.
itemsIndexed(fotos, key = { _, f -> f.id }, span = { indice, _ ->
if (indice == fotos.lastIndex) GridItemSpan(maxCurrentLineSpan) else GridItemSpan(1)
}) { _, foto -> Miniatura(foto) }
Hay dos restricciones que conviene tener presentes. Un span nunca puede superar el número de columnas de la línea, y con celdas adaptativas ese número no se conoce hasta la medida, razón por la cual el ámbito lo proporciona en vez de obligarte a calcularlo. Y un elemento con span mayor que uno rompe la alineación de la retícula a partir de ahí: la rejilla vuelve a empezar línea, dejando hueco si no cabía. Ese hueco es correcto, no un fallo, y aceptar que existe evita intentos de forzar colocaciones que la rejilla no promete.
La rejilla escalonada
Cuando los elementos tienen alturas naturalmente distintas —fotografías de proporciones variadas, tarjetas con textos de longitud desigual— una rejilla regular obliga a recortar o a dejar espacios muertos. La rejilla escalonada resuelve ese caso con un algoritmo sencillo y de consecuencias interesantes: mantiene varios carriles y coloca cada elemento nuevo en el carril que en ese momento está menos lleno.
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Adaptive(minSize = 160.dp),
verticalItemSpacing = 8.dp,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
item(span = StaggeredGridItemSpan.FullLine) { Cabecera() }
items(fotos, key = { it.id }, contentType = { "foto" }) { foto ->
Miniatura(foto, alturaRelativa = foto.proporcion)
}
}
El precio de ese algoritmo es que la colocación de un elemento depende de las alturas de todos los anteriores. En una rejilla regular la posición de un índice se puede calcular con una división; en una escalonada no se puede calcular sin haber medido todo lo que va antes, y la lista perezosa por definición no lo ha medido. De ahí dos comportamientos que sorprenden y que no son defectos sino consecuencias: la barra de desplazamiento y las estimaciones de posición son aproximadas, y al volver hacia atrás por una zona ya visitada la colocación se recalcula con las medidas reales, lo que puede producir pequeños reajustes.
flowchart TD
Q1{Los elementos tienen la misma altura} -->|Si| Q2{Cuantas columnas quiero}
Q1 -->|No| ESC[Rejilla escalonada con carriles]
Q2 -->|Numero exacto y cerrado| FIJA[Celdas fijas]
Q2 -->|Que dependa del ancho| ADAP[Celdas adaptativas]
Q2 -->|Tamano intrinseco del contenido| TAM[Celdas de tamano fijo]
ESC --> AVISO[Asume estimacion aproximada del scroll]
style ADAP fill:#a6e3a1,color:#11111b
style AVISO fill:#f9e2af,color:#11111bEl escalonado tiene además una diferencia de vocabulario que despista: aquí los spans no se expresan en número de columnas sino como una elección entre ocupar un solo carril u ocupar la línea completa. No es una limitación arbitraria sino una consecuencia del algoritmo: como los carriles avanzan a alturas distintas, un elemento que ocupara dos de tres carriles no tendría una posición vertical bien definida, mientras que uno que los ocupe todos siempre puede colocarse por debajo del carril más largo.
Si la altura de un elemento cambia después de haberse colocado —porque llegó la imagen, porque el texto se expandió, porque una fuente se cargó tarde— el algoritmo debe recolocar todo lo que venga después en ese carril. Eso ocurre durante el desplazamiento, cuesta una pasada de layout adicional y se percibe como un salto. En una rejilla regular el mismo cambio afecta solo a su fila; en una escalonada se propaga hacia abajo.
Hay una mitigación barata que casi nadie aplica: si conoces la proporción de cada elemento antes de cargarlo —y con imágenes de un servidor normalmente la conoces— puedes reservar la altura exacta antes de que la imagen llegue. Con eso el escalonado se estabiliza, desaparecen los saltos por contenido que cambia de tamaño al cargarse, y de paso se elimina una de las causas más frecuentes de reajuste de layout durante el scroll.
Cuándo la rejilla no es la respuesta
La rejilla perezosa es la herramienta correcta con una frecuencia menor de lo que su popularidad sugiere. Cuando el número de columnas es fijo, el contenido homogéneo y no hacen falta spans, una lista cuyos elementos sean filas con celdas repartidas por peso hace exactamente lo mismo con menos maquinaria y con un modelo mental más sencillo de depurar. El precio es que la agrupación en filas hay que hacerla en el modelo y que la unidad de reutilización pasa a ser la fila entera, lo cual es indiferente si todas se parecen.
En el extremo opuesto, cuando ninguna de las políticas de fábrica describe la colocación que necesitas —una portada con un elemento grande y varios pequeños alrededor, una línea temporal a dos columnas con conexiones— la salida no es forzar spans hasta que se parezca, sino construir un layout perezoso propio sobre la misma base de composición bajo demanda. Se hereda todo lo bueno, incluidas las claves, los tipos de contenido y la anticipación, y solo hay que aportar la lógica de medida y colocación.
Lista con filas manuales
Si el número de columnas es fijo y el contenido no necesita spans, agrupar los datos en filas dentro de una lista es más simple y evita la maquinaria de rejilla.
Rejilla adaptativa
Es la opción correcta en cuanto la pantalla deba funcionar en móvil, tablet, plegable y ventana redimensionable sin lógica condicional propia.
Escalonada
Solo cuando las alturas son genuinamente dispares y recortarlas empeora el producto. Asume a cambio la estimación aproximada del desplazamiento.
Layout perezoso a medida
Para colocaciones que ninguna de las anteriores describe. Reutiliza la misma base de composición bajo demanda, pero te obliga a implementar la medida.
Vale la pena mirar las tres estrategias de celda no como tres funciones de una biblioteca sino como tres respuestas distintas a una pregunta de diseño de sistemas: ¿en qué punto del programa vive el conocimiento sobre el tamaño de la pantalla? Con celdas fijas ese conocimiento vive arriba, en tu código, y el resultado es que cada nueva forma de pantalla —una tablet, un plegable abierto, una ventana arrastrada a media anchura, un escritorio— exige volver a tocar ese código y añadir una rama más. Con celdas adaptativas el conocimiento vive abajo, en el momento de la medida, donde el ancho real ya se conoce con certeza y no hay que adivinarlo. La diferencia parece de comodidad y es en realidad de arquitectura: en el primer caso has acoplado tu interfaz a un catálogo de dispositivos que caduca, y en el segundo has expresado una intención —“cada tarjeta necesita al menos este ancho para ser legible”— que sigue siendo cierta en hardware que todavía no existe. Esta es la misma lección que Android lleva quince años enseñando a golpes y que la interfaz declarativa por fin permite escribir con naturalidad: nunca preguntes por el dispositivo, declara la restricción. Cuando llegues a la unidad de interfaz adaptativa verás que toda ella es esta idea llevada al nivel de la pantalla completa, y habrás llegado allí habiéndola practicado ya en el sitio donde más barata sale de aprender, que es una rejilla de miniaturas.
- Monta una rejilla de miniaturas con celdas fijas de tres columnas y observa el resultado en horizontal y en tablet.
- Sustitúyela por celdas adaptativas con un ancho mínimo razonable y comprueba cuántas columnas salen en cada configuración.
- Añade una cabecera a ancho completo con span y explica por qué el número máximo de columnas se obtiene del ámbito y no de una constante.
- Convierte la rejilla en escalonada con alturas dispares y describe qué ocurre al desplazarte hacia abajo y volver.
- Reserva la altura de cada elemento a partir de su proporción conocida y razona qué reajustes desaparecen.