Las Grids en Android te permiten organizar colecciones de elementos en filas y columnas, es decir, cuadriculas de contenido.
Los ítems pueden ser distribuidos de forma vertical u horizontal y su distribución definirse uniforme o irregularmente (staggered grid) a lo largo del contenedor. Y por supuesto, este componente posee un scroll vertical u horizontal para la visualización de aquellos elementos ocultos por límite del tamaño de pantalla.
Normalmente las verás en casos de uso como galerías de imágenes, dashboards y catálogos de productos debido a que proporcionan excelente diseño para escaneo de información rica en contenido visual.
Con lo anterior dicho, en este tutorial aprenderás el paso a paso para crear cuadriculas con los componentes Lazy*Grid
de Jetpack Compose en tus aplicativos Android. Verás como:
- Crear una grid básica
- Crear una grid escalonada
- Personalizar espaciado de grilla e ítems
- Añadir animaciones a los ítems
Configurar Un Proyecto En Android Studio Para Grids
Abre Android Studio y crea un nuevo proyecto con la plantilla Empty Activity de Compose y nómbralo Grids En Android.
Al igual que todos los tutoriales de Compose que hemos visto hasta el momento, este ejemplo requerirá de añadir como dependencias de Gradle.
Definir Actividad Principal
Abre la actividad creada por defecto MainActivity
y renómbrala como GridsActivity
. Luego reemplaza el código de la plantilla por el siguiente:
class GridsActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
GridsTheme {
GridsScreen()
}
}
}
}
Como ves, dentro de setContent()
invocaremos a nuestra función componible GridsScreen()
, la cual tendrá la definición de los ejemplos que veremos.
Diseñar Pantalla Principal
La pantalla principal del ejemplo tiene el componente base Scaffold
que nos permite añadir slots para Top App Bar y Floating Action Button.
Donde el contenido principal será la función componible que generemos para cada ejemplo de Grids. A partir de lo anterior, GridsScreen()
se verá así:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GridsScreen() {
var isConfigPanelOpen by rememberSaveable { mutableStateOf(false) }
// Código omitido
Scaffold(
topBar = {
TopBar(
onConfigClick = { isConfigPanelOpen = true },
verticalScrollBehavior = verticalScrollBehavior
)
},
floatingActionButton = { Fab(viewModel::addItem) }
) {
// Aquí invocas a los ejemplos que crearemos
}
if (isConfigPanelOpen) {
ConfigPanel(
// Código omitido
)
}
}
Más adelante verás que usaremos el Fab para añadir ítems a la cuadrícula con el fin de visualizar animaciones. Por otro lado, el action button en la app bar permite desplegar una Bottom Sheet (ConfigPanel
) para visualizar los diferentes ejemplos, aun así, la construcción será omitida para enfocarnos en las grids.
Descargar Código Completo Desde GitHub
Por cuestiones de simplicidad, varios elementos ajenos a la explicación de Grids no serán comentados a lo largo del tutorial. Por lo que puedes ver el código completo desde el repositorio de la guía de UI en Github para que comprendas el panorama completo:
Crear Grid Vertical
LazyVerticalGrid
en Compose AndroidNuestro primer ejemplo será el caso básico de una cuadricula con dos columnas como ves en la ilustración anterior.
Crear Data Class De Estado
Si observas el diseño del ejemplo, necesitamos visualizar elementos con un nombre y el color de su background. Pero como estamos simulando datos hipotéticos y desconocemos que altura pudiesen llegar a tener los ítems, también necesitaremos una propiedad para esta característica.
La sentencia anterior la podemos materializar en una clase de datos llamada GridItemUiModel
de la siguiente manera:
data class GridItemUiModel(
val id: String,
val title: String,
val hexColor: Long = range32BitsColor.random()
) {
companion object {
private val range32BitsColor = (0x00000000..0xFFFFFFFF)
var idCounter = 1
fun newId() = idCounter++.toString()
const val MIN_SIZE = 120
}
}
Donde:
id
: Identiticador del ítem. Su valor será generado a través denewId()
, el cual incrementa un contador enteroidCounter
.title
: Título del ítemhexColor
: Long hexadecimal que representa el color del backgroundrange32BitsColor
: Rango de selección de colorMIN_SIZE
: Constante con el tamaño en Dps del ítem
Con GridItemUiModel
ya podemos crear una colección que represente el conjunto de elementos a desplegar en pantalla.
Crear ViewModel Para Grid
Normalmente la colección de ítems que deseamos proyectar viene de otra capa (datos o aplicación), por lo que necesitaremos crear un ViewModel
que actué como intermediario para obtener dichos datos y formatearlos.
En nuestro ejercicio no contamos con dicho requerimiento por cuestiones de practicidad, no obstante, simularemos un origen del estado con una colección en memoria creada programáticamente. Con esto en mente, añade una nueva clase GridsViewModel
al proyecto con este código:
class GridsViewModel : ViewModel() {
private val _items = MutableStateFlow<List<GridItemUiModel>>(
List(20) { newItem() }.reversed()
)
val items = _items.asStateFlow()
private fun newItem(): GridItemUiModel {
val itemId = GridItemUiModel.newId()
return GridItemUiModel(itemId, itemId)
}
}
En él declaramos a las propiedades _items
e items
para expresar mutabilidad e inmutabilidad del estado a través de flows. Aquí la lista inicia con veinte elementos GridItemUiModel
, creada a partir de la función constructora List()
.
Normalmente no necesitamos de la propiedad mutable, pero como más adelante añadiremos y removeremos elementos, cobra sentido en este caso.
Crear Componente Para Item De Grilla
Nuestros ítems tienen como contenedor principal un Card delineada que envuelve un componente de texto. De modo que al interior de un nuevo archivo llamado GridItem.kt
, añadimos la función GridItem()
:
@Composable
internal fun GridItem(
modifier: Modifier = Modifier,
item: GridItemUiModel,
onClick: () -> Unit
) {
OutlinedCard(
modifier = modifier.clickable(onClick = onClick),
colors = CardDefaults.cardColors(containerColor = Color(item.hexColor)),
) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(
text = item.title,
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.Companion.padding(16.dp)
)
}
}
}
@Composable
@Preview
private fun GridItemPreview() {
GridItem(
modifier = Modifier.size(200.dp),
item = GridItemUiModel("1", "1"),
onClick = {}
)
}
Como ves, el componente recibe un modificador para permitir a los padres configurar los aspectos de UI del ítem (tenemos varios ejemplos que usan diferentes modificadores width()
y height()
).
También está el parámetro GridItemUiModel
nos permite vincular las propiedades a la vista.
Y por último la lambda onClick
que permite asociar acciones cuando ítem sea clickado.
Usar Componente LazyVerticalGrid De Compose
La función componible que representa una grid vertical en Compose es LazyVertical
. Esta requiere de dos parámetros obligatorios para su invocación:Grid
()
columns
: Describe al cantidad y tamaño de las columnas de la cuadrícula. Es de tipoGridCells
, el cual provee tres modosFixed
: Define un número de columnas fijas cuyo ancho será el ancho del padre dividido la cantidad de las mismasAdaptive
: Dividirá el ancho de la lista entre el ancho que pases como parámetro para calcular el número de columnas. El espacio restante es distribuido equitativamente en las columnas definidasFixedSize
: Similar aAdaptive
, solo que el ancho restante no es distribuido
content
: Una lambda con recibidor del tipoLazyGridScope
, que al igual queLazyColumn
, provee métodositems()
eitem()
para construir tu cuadrícula
A partir de la descripción técnica anterior, implementemos nuestra primera Grid.
GridCells.Fixed
LazyVerticalGrid
con GridCells.Fixed
con valores 2, 3 y 4.Añade un nuevo archivo llamado VerticalGridExample.kt
y declara una función @Composable
con el mismo nombre. La idea es recibir como parámetro el modificador, una lista de elementos GridItemUiModel
y la lambda que procesa el click sobre un ítem.
Adicionalmente, usaremos GridCells.Fixed
con el argumento 2 para representar dos columnas en la cuadrícula:
@Composable
fun VerticalGridExample(
items: List<GridItemUiModel>,
modifier: Modifier = Modifier,
onGridItemClick: (GridItemUiModel) -> Unit
) {
LazyVerticalGrid(
modifier = modifier,
columns = GridCells.Fixed(2), // ← Número de columnas
) {
items(items = items) {
GridItem(
modifier = Modifier.height(GridItemUiModel.MIN_SIZE.dp),
item = it,
onClick = { onGridItemClick(it) }
)
}
}
}
GridCells.Adaptive
LazyVerticalGrid
con GridCells.Adaptive
con valores de 103, 154, 180 y 206dpSi reemplazas el valor de columns
del código por el tipo GridCells.Adaptive
y un argumento de 120.dp
, la función LazyVerticalGrid
distribuirá los elementos en 3 columnas.
@Composable
fun VerticalGridExample(
//...
) {
LazyVerticalGrid(
modifier = modifier,
columns = GridCells.Adaptive(120.dp), // ← Ancho de columna
) {
// ..
}
}
En este ejemplo usamos el emulador Pixel 9 con 412dp de ancho. Esto significa que 412/120=3.43
, lo que nos deja 3 columnas. La porción restante de 52dp es distribuida entre las columnas, dejándolas con un tamaño aproximado de 137.3dp.
GridCells.FixedSize
LazyVerticalGrid
con GridCells.
FixedSize. Nota como el espacio restante se mantieneAhora fijemos el tamaño de las columnas a 206dp con GridCells.FixedSize
para experimentar el resultado:
@Composable
fun VerticalGridExample(
//...
) {
LazyVerticalGrid(
modifier = modifier,
columns = GridCells.FixedSize(206.dp), // ← Ancho de columna
) {
// ..
}
}
Si observas la captura de pantalla del emulador, el resultado nos da 1 columna, pero esta vez el espacio restante (206dp) no es distribuido, el espacio queda libre.
Crear Grid Horizontal
LazyHorizontalGrid
con 3 filasSi necesitas crear una Grid horizontal, usa el componente LazyHorizontalGrid()
. Su firma es exactamente igual que la de LazyVerticalGrid
, con la diferencia de que se especifica el uso de filas (rows
) en lugar de columnas.
Añade un nuevo archivo HorizontalGridExample.kt
y codifica una cuadrilla con 3 filas:
@Composable
fun HorizontalGridExample(
items: List<GridItemUiModel>,
modifier: Modifier = Modifier,
onGridItemClick: (GridItemUiModel) -> Unit = {}
) {
LazyHorizontalGrid(
modifier = modifier.height(412.dp),
rows = GridCells.Fixed(3)
content = {
items(items = items) {
GridItem(
modifier = Modifier.width(GridItemUiModel.MIN_SIZE.dp),
item = it,
onClick = { onGridItemClick(it) }
)
}
}
)
}
En el caso de usar GridCells.Adaptive
y GridsCells.FixedSize
, el espacio será distribuido horizontalmente de la misma forma que percibimos anteriormente.
Crear Staggered Grid Vertical
LazyVerticalStaggeredGrid
con StaggeredGridCells.Fixed(3)
Una Staggered Grid es una cuadricula que organiza a sus elementos de forma escalonada sobre el contenedor. Esto quiere decir que sus ítems pueden tener diferentes alturas o anchos con el fin de producir un efecto visual de mosaico.
Para crear una cuadricula escalonada vertical usamos el componente LazyVerticalStaggeredGrid
. Este recibe casi los mismos parámetros que su forma uniforme, la diferencia radica en que las interfaces para los tipos varían. El parámetro de contenido usa a LazyStaggeredGridScope
y las columnas a StaggeredGridCells
.
Con esto en mente, crea un nuevo archivo llamado VerticalStaggeredGridExample.kt
y codifica una cuadricula con tres columnas a través de StaggeredGridCells.Fixed
:
@Composable
fun VerticalStaggeredGridExample(
items: List<GridItemUiModel>,
modifier: Modifier = Modifier,
onGridItemClick: (GridItemUiModel) -> Unit = {}
) {
LazyVerticalStaggeredGrid(
modifier = modifier,
columns = StaggeredGridCells.Fixed(3)
) {
items(items = items) {
GridItem(
modifier = Modifier.height(it.sizeForStaggered.dp),
item = it,
onClick = { onGridItemClick(it) }
)
}
}
}
Para simular las diferentes alturas de nuestros ítems hemos usado una propiedad inexistente GridItemUiModel.sizeForStaggered
. Así que añadámosla para ver su propósito:
data class GridItemUiModel(
//...
) {
val sizeForStaggered = MIN_SIZE * rangeForRandom.random()
companion object {
private val rangeForRandom = 1..3
//...
}
}
Su declaración se basa en la elección aleatoria de tres posibles tamaños: 1x, 2x y 3x del valor de MIN_SIZE
. De esta forma podemos simular que existe contenido con diferentes longitudes que obliga expandir al ítem de la grilla para mostrarlo
Crear Staggered Grid Horizontal
LazyHorizontalStaggeredGrid
con argumento StaggeredGridCells.Fixed(3)
en rows
En contraste de la vertical, una cuadricula escalonada horizontal aplicará la distribución de los ítems desde su ancho. El componente de Compose que la representa es LazyHorizontalStaggeredGrid
. Su invocación es similar a la vertical, solo que no tomamos a columns
si no rows
.
Ilustremos su uso creando una staggered grid horizontal con cuatro filas en un nuevo archivo HorizontalStaggeredGridExample.kt
:
@Composable
fun HorizontalStaggeredGridExample(
items: List<GridItemUiModel>,
modifier: Modifier = Modifier,
onGridItemClick: (GridItemUiModel) -> Unit = {}
) {
LazyHorizontalStaggeredGrid(
modifier = modifier.height(412.dp),
rows = StaggeredGridCells.Fixed(3)
) {
items(items = items) {
GridItem(
modifier = Modifier.width(it.sizeForStaggered.dp),
item = it,
onClick = { onGridItemClick(it) }
)
}
}
}
Esta vez accedemos a sizeForStaggered
pero para variar el ancho de los ítems a través del modificador width()
.
Personalizar Grid
Ajustar Padding Del Contenido
contentPadding
con valores del rango [0,16]dpHasta el momento todos los ejemplos creados están desplegados hasta los límites del contenedor de la Grid. Situación que no es ideal, ya que necesitamos proporcionar espacios para promover la organización visual en nuestras pantallas.
Afortunadamente y como habrás explorado, los componentes Compose de cuadriculas traen consigo un parámetro llamado contentPadding
. Este representa el relleno de la cuadricula, es decir, el espacio entre el borde y los ítems.
Por ejemplo, añadamos 16 Dps de padding a nuestro ejemplo VerticalGridExample
:
@Composable
fun VerticalGridExample(
//...
) {
LazyVerticalGrid(
contentPadding = PaddingValues(all = 16.dp)
) {
// ..
}
}
Tal como se aprecia, contentPadding
es del tipo PaddingValues
, el cual puede ser construido con varias funciones especificando los bordes del contenedor. Nosotros usamos la firma con all
para asignar el mismo valor a los cuatro bordes, pero también puedes usar PaddingValues(horizontal, vertical)
o PadddingValues(start, top, end, bottom)
.
Ajustar Espaciado Entre Items
También es posible añadir espacio vertical y horizontal entre los ítems de las Grids a partir de dos parámetros en los componentes.
Para las versiones básicas de las cuadriculas empleamos a verticalArrangement
para el espaciado vertical y horizontalArrangement
para el horizontal. Y el argumento a pasar lo construimos con la función Arrangement.spacedBy()
, la cual recibe los Dps del espacio. Por ejemplo:
@Composable
fun VerticalGridExample(
//...
) {
LazyVerticalGrid(
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
// ..
}
}
En el caso de las versiones escalonadas, la cuadricula vertical en lugar de verticalArrangement
usa un parámetro verticalItemSpacing
. Similar sucede con la horizontal, no tiene a horizontalArrangement
si no a horizontalItemSpacing
.
Por ejemplo, pasemos 8 Dps en ambos tipos de staggered grid:
@Composable
fun VerticalStaggeredGridExample(
//...
) {
LazyVerticalStaggeredGrid(
//...
verticalItemSpacing = 8.dp,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
//...
}
}
@Composable
fun HorizontalStaggeredGridExample(
//...
) {
LazyHorizontalStaggeredGrid(
//...
horizontalItemSpacing = 8.dp,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
//...
}
}
Animar Items De Grid
Actualmente si la lista de ítems que tenemos cambia su tamaño, las cuadriculas creadas como ejemplo no mostrarán una animación para representar la actualización visual del contenido. Sin embargo, es fácil añadir una animación de Fade In y Fade Out con el modificador animateItem()
.
Para probar las animaciones necesitaremos:
- Una clave distintiva a cada ítem de la cuadricula al construirla con el DSL que pasamos como argumento en
content
- Un método
addItem()
en el view model para añadir un ítem a la cuadrilla - Un método
removeItem()
en el view model para remover un ítem a la cuadrilla
Resolvamos estas condiciones.
Vincular Key A Item De Grid
El modificador animateItem()
requiere que pasemos una clave única para habilitar las animaciones en nuestra Grid. Debido a que nuestra clase GridItemUiModel
contiene una propiedad id
, este es un buen valor para pasar en nuestras grids de ejemplo:
@Composable
fun VerticalGridExample(
//...
) {
LazyVerticalGrid(
//...
) {
items(items = items, key = { it.id } ) { // ← Vinculación de clave
GridItem(
modifier = Modifier
.animateItem() // ← Modificador para animar elemento
.height(GridItemUiModel.MIN_SIZE.dp),
//...
)
}
}
}
Como puedes notar, pasamos el id en el parámetro key
de la función items()
donde construimos cualquiera de nuestras grillas. Con ello las animaciones son habilitadas y ahora debemos modificar nuestro estado para observar el resultado.
Añadir Item A Grid
animateItem()
al agregar un elemento a la GridLa representación de la creación de un nuevo elemento la representaremos a través de un método addItem()
desde GridsViewModel
:
class GridsViewModel : ViewModel() {
//...
fun addItem() {
_items.update {
listOf(newItem()) + it
}
}
private fun newItem(): GridItemUiModel {
val itemId = GridItemUiModel.newId()
return GridItemUiModel(itemId, itemId)
}
}
Lo que hacemos es reemplazar el valor actual de _items
por una nueva lista, resultado de sumar el nuevo elemento y la lista actual.
Con esto resuelto, vamos al Floating Action Button de GridsScreen
y le pasamos la referencia del método additem()
a su parámetro onClick
.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GridsScreen(viewModel: GridsViewModel = viewModel()) {
val gridItems by viewModel.items.collectAsStateWithLifecycle()
Scaffold(
//...
floatingActionButton = { Fab(viewModel::addItem) } // ← Vinculo a view model
) {
//...
}
//...
}
Luego de que el código anterior sea implementado, podrás obtener el resultado observado en la animación mostrada al inicio. El nuevo ítem añadido aparecerá suavemente y los demás serán reordenados.
Remover Item De Grid
Ahora en GridsViewModel
creamos el método removeItem()
para remover un elemento del estado actual. Crear una nueva lista con un elemento menos se logra usando el operador menos -
, donde el minuendo es la lista y el sustraendo es el GridItemUiModel
a eliminar.
class GridsViewModel : ViewModel() {
//...
fun removeItem(item: GridItemUiModel) {
_items.update { it - item }
}
}
En virtud de que la eliminación se da cuando tocamos los ítems de la cuadrícula, entonces añadimos un parámetro a las funciones de ejemplo llamado onGridItemClick
. Dicha lambda será pasada en onClick
de GridItem()
:
@Composable
fun VerticalGridExample(
//...
onGridItemClick: (GridItemUiModel) -> Unit // ← Lambda al tocar ítem
) {
LazyVerticalGrid(
//...
) {
items(items = items, key = { it.id }) {
GridItem(
//...
onClick = { onGridItemClick(it) } // ← Vinculación al ítem
)
}
}
}
Por lo que desde GridsScreen()
pasas el valor al componente de grilla con la referencia GridsViewModel::removeItem()
:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GridsScreen(viewModel: GridsViewModel = viewModel()) {
//...
Scaffold(
//...
) {
LazyVerticalGrid(
//...
onGridItemClick = viewModel::removeItem
)
}
//...
}
Y como viste en el gif del inicio, cada que toques un ítem de la grilla experimentarás como el elemento eliminado se desvanece y los demás son desplazados para reordenar el espacio
Conclusión
Jetpack Compose proporciona un conjunto robusto de componentes para construir grids personalizables mediante LazyVerticalGrid
, LazyHorizontalGrid
, LazyVerticalStaggeredGrid
y LazyHorizontalStaggeredGrid
. A través de estas APIs, es posible definir estructuras uniformes o escalonadas, configurar el número y tipo de celdas con GridCells
, controlar el espaciado entre elementos y aplicar padding interno al contenedor.
Además, la incorporación de animaciones mediante animateItem()
permite gestionar cambios en la colección de forma visualmente fluida, mejorando la experiencia del usuario. El uso de claves únicas por ítem y la gestión de estado desde el ViewModel
aseguran un comportamiento predecible y fácil de mantener.
En resumen, Compose simplifica considerablemente la creación de interfaces en grid con alto grado de personalización, manteniendo al mismo tiempo una arquitectura limpia y escalable.
¿Te fue útil este ejemplo? Envíame un mensaje en mi servidor de Discord o comparte este artículo en redes.
¿Estás Creando Una App De tareas?
Te comparto una plantilla Android profesional con arquitectura limpia, interfaz moderna y funcionalidades listas para usar. Ideal para acelerar tu desarrollo.

Únete Al Discord De Develou
Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!