Jetpack Compose, Android’s modern UI toolkit, provides a powerful and flexible way to build user interfaces. While it offers built-in layouts like Column and Row, creating complex layouts like a staggered grid requires a bit more effort. A staggered grid, also known as a masonry layout, is a grid where the sizes of the items may vary, creating an appealing, dynamic look.
What is a Staggered Grid Layout?
A staggered grid layout arranges items in a grid-like structure, but the sizes (especially the heights) of the items are not uniform. This variation in size creates a dynamic and visually appealing effect. This type of layout is often seen in Pinterest and other image-heavy applications.
Why Use a Staggered Grid?
- Visual Appeal: Provides a dynamic and interesting layout.
- Content Emphasis: Allows certain items to stand out based on size.
- Efficient Use of Space: Adapts well to varying content sizes, maximizing screen real estate.
How to Implement a Staggered Grid Layout in Jetpack Compose
Currently, Jetpack Compose does not offer a built-in staggered grid layout. However, we can implement one using custom layouts or libraries.
Method 1: Using LazyVerticalStaggeredGrid (Requires androidx.compose.foundation:foundation-layout 1.6.0 or later)
The LazyVerticalStaggeredGrid API provides a way to build staggered grids with varying spans using the StaggeredGridCells and LazyStaggeredGridItemScope.StaggeredGridItemSpan APIs.
Step 1: Add Dependency
Ensure you have the latest Compose Foundation dependency in your build.gradle file:
dependencies {
implementation("androidx.compose.foundation:foundation-layout:1.6.0") // or newer
}
Step 2: Implement LazyVerticalStaggeredGrid
Here’s an example of how to create a staggered grid using LazyVerticalStaggeredGrid:
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlin.random.Random
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun StaggeredGridLayout() {
val items = (1..20).map { "Item $it" }
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Adaptive(minSize = 120.dp),
modifier = Modifier.fillMaxWidth(),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(items.size) { index ->
val randomHeight = Random.nextInt(50, 150).dp // Different heights
val item = items[index]
Card(
modifier = Modifier.fillMaxWidth()
) {
Text(
text = item,
modifier = Modifier.padding(8.dp)
)
}
}
}
}
@Preview(showBackground = true)
@Composable
fun StaggeredGridLayoutPreview() {
StaggeredGridLayout()
}
In this example:
LazyVerticalStaggeredGridcreates a vertically scrolling staggered grid.StaggeredGridCells.Adaptive(minSize = 120.dp)sets up the columns to adapt to the screen size with a minimum width of 120dp.- Each item has a random height between 50 and 150 dp, creating the staggered effect.
Method 2: Using a Custom Layout
For more control and customization, you can create a custom staggered grid layout using Compose’s Layout composable.
Step 1: Create a Custom Layout Composable
Implement a custom layout composable that manually positions the children in a staggered manner.
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.Dp
import androidx.compose.foundation.layout.*
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.Constraints
import kotlin.random.Random
@Composable
fun StaggeredGrid(
modifier: Modifier = Modifier,
columnCount: Int = 2,
horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(8.dp),
content: @Composable () -> Unit
) {
Layout(
content = content,
modifier = modifier
) { measurables, constraints ->
val columnWidth = (constraints.maxWidth / columnCount)
val itemConstraints = Constraints.fixedWidth(columnWidth)
val placeables = measurables.map { measurable ->
measurable.measure(itemConstraints)
}
val columnHeights = IntArray(columnCount) { 0 } // Heights of each column
layout(
width = constraints.maxWidth,
height = placeables.sumOf { it.height } // Simplified height calculation, can be improved
) {
placeables.forEachIndexed { index, placeable ->
val shortestColumnIndex = columnHeights.indexOf(columnHeights.minOrNull() ?: 0)
val x = shortestColumnIndex * columnWidth
val y = columnHeights[shortestColumnIndex]
placeable.placeRelative(x = x, y = y)
columnHeights[shortestColumnIndex] += placeable.height
}
}
}
}
@Preview(showBackground = true)
@Composable
fun StaggeredGridPreview() {
val items = (1..10).map { "Item $it" }
StaggeredGrid(columnCount = 2, modifier = Modifier.padding(16.dp)) {
items.forEach { item ->
val randomHeight = Random.nextInt(50, 150).dp
Card(modifier = Modifier.height(randomHeight)) {
Box(contentAlignment = Alignment.Center) {
Text(text = item)
}
}
}
}
}
Key components in this custom layout:
- Column Width Calculation: Determines the width of each column based on the provided
columnCount. - Item Measurement: Measures each item with fixed-width constraints to fit within the columns.
- Column Height Tracking: An array
columnHeightstracks the height of each column to find the shortest column for placing the next item. - Placement Logic: Iterates through each item, places it in the shortest column, and updates the column’s height.
Step 2: Usage
Use the StaggeredGrid composable with your content:
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlin.random.Random
@Composable
fun StaggeredGridItem(text: String) {
val randomHeight = Random.nextInt(50, 150).dp
Card(modifier = Modifier.height(randomHeight).padding(4.dp)) {
Text(text = text, modifier = Modifier.padding(8.dp))
}
}
@Composable
fun StaggeredGridExample() {
val items = listOf("Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6")
StaggeredGrid(columnCount = 2, modifier = Modifier.padding(16.dp)) {
items.forEach { item ->
StaggeredGridItem(text = item)
}
}
}
@Preview(showBackground = true)
@Composable
fun StaggeredGridExamplePreview() {
StaggeredGridExample()
}
Conclusion
Creating a staggered grid layout in Jetpack Compose can be achieved through LazyVerticalStaggeredGrid or a custom layout approach. While LazyVerticalStaggeredGrid offers simplicity for basic implementations, a custom layout provides greater control and flexibility for complex designs. Choose the method that best fits your project’s requirements and level of customization needed. Properly implementing a staggered grid can greatly enhance the visual appeal and user experience of your Android applications.