Learning Android Development
StaggeredVerticalGrid of Android Jetpack Compose
Using Jetpack Compose Layout to make StaggeredVerificatlGrid
In Jetpack Compose, even though we have LazyColumnFor
to do what RecyclerView
has, but there’s not readily available StagerredGridLayout
provided.
Fortunately, Google provided a sample of how this can be implemented as per this StackOverflow shared.
With that, I used to randomly generate some stagged cards and explore how it is done.

Using StaggeredVerticalGrid
To use it, it is simple. Just provide the maxColumnWidth
for the items (e.g. Card) and then provide the item (e.g. Card) accordingly.
StaggeredVerticalGrid(
maxColumnWidth = columnWidth.value.dp,
modifier = Modifier.padding(4.dp)
) {
(1..100).forEach {
Card(it.toString(), columnWidth)
}
}
In my example, I generate a random maxColumnWidth
so we can get different column count when I regenerate them. I also provide 100 Cards, that randomly generate the color and height, to make the staggering view nicer.
Understand how Staggered is done
I have modified the original StaggeredVerticalGrid to be more compute efficient by storing the initial computer locations, but overall they are very similar to the original algorithm.
It is using the Layout
composable function.
@Composable
fun StaggeredVerticalGrid(
modifier: Modifier = Modifier,
maxColumnWidth: Dp,
content: @Composable () -> Unit
) {
Layout(
content = content,
modifier = modifier
) { measurables, constraints ->
// The algorithm generate staggered vertical grid
}
}
The algorithm is broken into 4 parts.
1. Calculate the right width for the columns
val columns =
ceil(constraints.maxWidth / maxColumnWidth.toPx()).toInt()
val columnWidth = constraints.maxWidth / columns
val itemConstraints = constraints.copy(maxWidth = columnWidth)
Given the maxColumnWidth
, the algorithm will first calculate the with it can stretch the most for each of the items.

- Calculate the total columns possible using
maxWidth
divided by themaxColumnWidth
- From the column counts, when we can get the exact width that can be allocated to each item without violating the
maxColumnWidth
- Then copy all the constraint over, with only modifying the
maxWidth
for each item.
2. Compute the total height of the layout
val colHeights = IntArray(columns) { 0 }
val placeables = measurables.map { measurable ->
val column = shortestColumn(colHeights)
val placeable = measurable.measure(itemConstraints)
placeableXY[placeable]
= Pair(columnWidth * column, colHeights[column])
colHeights[column] += placeable.height
placeable
}
For each of the measurable
(e.g. Card), we’ll try to place the column that is the shortest height.
From the measurable
, we’ll get the exact item i.e. placeable
to get the height of it to be added to colHeight
.
The colHeight
is used to keep track of the height of each column, so that we can place the placeable
into the shortest column area, and also we can get the final height needed for the layout.

As shown in above the diagram, you’ll notice each placeable
is always placed at the shortest column first.
3. Get the height of the layout
And finally the longest height ie. colHeight[0]
is used to determined the layout height.
val height = colHeights.maxOrNull()
?.coerceIn(constraints.minHeight, constraints.maxHeight)
?: constraints.minHeight

4. Place all the peaceable (i.e. card) in the layout
Lastly, after the layout size is determined, we can then place each of the cards accordingly to the X, Y location that was previously stored in placeableXY
.
layout(
width = constraints.maxWidth,
height = height
) {
placeables.forEach { placeable ->
placeable.place(
x = placeableXY.getValue(placeable).first,
y = placeableXY.getValue(placeable).second
)
}
}

That’s it. Now not only you know how to use StaggeredVerticalGrid
, but also how to use Layout
and make it works for you better.
You can get the code here. Cheers.