StaggeredVerticalGrid of Android Jetpack Compose
Tags: Mobile App Development, Jetpack Compose, Android, AndroidDev, Android App Development

Learning Android Development

StaggeredVerticalGrid of Android Jetpack Compose

Using Jetpack Compose Layout to make StaggeredVerificatlGrid

Photo by Julian Hochgesang on Unsplash

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 the maxColumnWidth 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

Notice the layout is represented in yellow color

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 .