The Jetpack Compose Staggered GridView is implemented using the LazyVerticalStaggeredGrid composable, which was introduced as part of Jetpack Compose's lazy layout components. This powerful UI component enables developers to create Pinterest-style grid layouts where items can have different heights while maintaining a consistent column width.
Let's explore the essential properties that you'll need to understand when working with a Jetpack Compose Staggered GridView:
The columns
parameter defines how many columns your staggered grid will display. You can set this using:
columns = StaggeredGridCells.Fixed(2) // For a fixed number of columns
Or you can make it adaptive to screen width:
columns = StaggeredGridCells.Adaptive(minSize = 128.dp) // Fits as many columns as possible with minimum width
The Fixed option creates a predetermined number of columns, while Adaptive adjusts the number of columns based on available screen width, ensuring each column is at least the specified minimum width.
The contentPadding
parameter allows you to add padding around the entire grid:
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
This property is particularly useful for creating margins between your grid and other UI elements.
The verticalItemSpacing
parameter controls the vertical spacing between items:
verticalItemSpacing = 8.dp
This creates consistent vertical gaps between items in the Jetpack Compose Staggered GridView.
Similar to vertical spacing, horizontalArrangement
controls the spacing between columns:
horizontalArrangement = Arrangement.spacedBy(8.dp)
This ensures consistent horizontal spacing in your Jetpack Compose Staggered GridView.
The content of your staggered grid is defined using the items
scope, where you can specify the number of items and their content:
items(count = yourDataList.size) { index ->
// Your item content here
}
You can also use itemsIndexed
if you need access to both the index and the item:
itemsIndexed(yourDataList) { index, item ->
// Your item content that uses both index and item
}
Now that we understand the key properties of a Jetpack Compose Staggered GridView, let's see how to implement one in your Android application.
Here's a simple implementation of a Jetpack Compose Staggered GridView that displays a list of items with varying heights:
@Composable
fun SimpleStaggeredGrid() {
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(2),
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalItemSpacing = 8.dp
) {
items(20) { index ->
// Each item has a different height based on the index
val height = if (index % 3 == 0) 150.dp else if (index % 3 == 1) 200.dp else 100.dp
Card(
modifier = Modifier
.fillMaxWidth()
.height(height),
elevation = CardDefaults.cardElevation(4.dp)
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(text = "Item $index")
}
}
}
}
}
This creates a simple Jetpack Compose Staggered GridView with two columns and items of varying heights.
A key strength of the Jetpack Compose Staggered GridView is its ability to handle content with naturally varying heights. Here's how you can create items that size themselves based on their content:
@Composable
fun DynamicHeightStaggeredGrid(items: List<StaggeredItem>) {
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(2),
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalItemSpacing = 8.dp
) {
itemsIndexed(items) { _, item ->
Card(
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(4.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = item.title,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 8.dp)
)
Text(
text = item.description,
style = MaterialTheme.typography.bodyMedium
)
if (item.imageRes != null) {
Image(
painter = painterResource(id = item.imageRes),
contentDescription = item.title,
modifier = Modifier
.padding(top = 8.dp)
.fillMaxWidth()
.height(120.dp),
contentScale = ContentScale.Crop
)
}
}
}
}
}
}
data class StaggeredItem(
val title: String,
val description: String,
val imageRes: Int? = null
)
In this example, each item's height is determined by its content (title, description, and optional image), creating a natural staggered effect in the Jetpack Compose Staggered GridView.
Adding click functionality to your Jetpack Compose Staggered GridView items is straightforward:
@Composable
fun ClickableStaggeredGrid(items: List<StaggeredItem>, onItemClick: (StaggeredItem) -> Unit) {
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(2),
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalItemSpacing = 8.dp
) {
itemsIndexed(items) { _, item ->
Card(
modifier = Modifier
.fillMaxWidth()
.clickable { onItemClick(item) },
elevation = CardDefaults.cardElevation(4.dp)
) {
// Item content as before
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = item.title,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 8.dp)
)
Text(
text = item.description,
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
}
By adding the clickable
modifier, you can handle click events for each item in your Jetpack Compose Staggered GridView.
Here's a full, runnable example of a Jetpack Compose Staggered GridView implementation:
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.itemsIndexed
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.tooling.preview.Preview
import android.widget.Toast
import androidx.compose.ui.platform.LocalContext
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
StaggeredGridScreen()
}
}
}
}
}
@Composable
fun StaggeredGridScreen() {
val context = LocalContext.current
// Sample data for our staggered grid
val items = remember {
listOf(
GridItem(
"Exploring Jetpack Compose",
"Jetpack Compose is Android's modern toolkit for building native UI. It simplifies and accelerates UI development on Android.",
R.drawable.sample_image_1,
"https://developer.android.com/jetpack/compose"
),
GridItem(
"Building with Material 3",
"Material 3 is the latest version of Google's open-source design system.",
R.drawable.sample_image_2,
"https://m3.material.io/"
),
GridItem(
"Kotlin Flows",
"A Flow is a type that can emit multiple values sequentially, as opposed to suspend functions that return only a single value.",
null,
"https://kotlinlang.org/docs/flow.html"
),
GridItem(
"Android Architecture",
"App architecture guides you to design your app so that it's robust, testable, and maintainable.",
R.drawable.sample_image_3,
"https://developer.android.com opic/architecture"
),
GridItem(
"Coroutines",
"A coroutine is a concurrency design pattern that you can use on Android to simplify code that executes asynchronously.",
null,
"https://kotlinlang.org/docs/coroutines-overview.html"
),
GridItem(
"Staggered GridView",
"The LazyVerticalStaggeredGrid is a powerful composable for creating Pinterest-style layouts in your Android app using Jetpack Compose.",
R.drawable.sample_image_4,
null
),
GridItem(
"Animations in Compose",
"Jetpack Compose offers a powerful animation system that makes it easy to implement a variety of animations with minimal code.",
R.drawable.sample_image_5,
"https://developer.android.com/jetpack/compose/animation"
),
GridItem(
"Navigation in Compose",
"Jetpack Navigation Compose provides the Navigation component support for Jetpack Compose applications.",
null,
"https://developer.android.com/jetpack/compose
avigation"
)
)
}
Column(modifier = Modifier.fillMaxSize()) {
// App Bar
TopAppBar(
title = { Text("Jetpack Compose Staggered GridView") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
)
)
// Staggered Grid
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(2),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalItemSpacing = 16.dp,
modifier = Modifier.fillMaxSize()
) {
itemsIndexed(items) { _, item ->
StaggeredGridItem(
item = item,
onClick = {
Toast.makeText(context, "Clicked: ${item.title}", Toast.LENGTH_SHORT).show()
}
)
}
}
}
}
@Composable
fun StaggeredGridItem(item: GridItem, onClick: () -> Unit) {
Card(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick),
elevation = CardDefaults.cardElevation(
defaultElevation = 4.dp
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = item.title,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 8.dp)
)
Text(
text = item.description,
style = MaterialTheme.typography.bodyMedium,
maxLines = 4,
overflow = TextOverflow.Ellipsis
)
item.imageRes?.let { imageRes ->
Spacer(modifier = Modifier.height(8.dp))
Image(
painter = painterResource(id = imageRes),
contentDescription = item.title,
modifier = Modifier
.fillMaxWidth()
.height(120.dp),
contentScale = ContentScale.Crop
)
}
}
}
}
data class GridItem(
val title: String,
val description: String,
val imageRes: Int? = null,
val link: String? = null
)
@Preview
@Composable
fun StaggeredGridScreenPreview() {
MaterialTheme {
Surface {
StaggeredGridScreen()
}
}
}
You can implement infinite scrolling with a Jetpack Compose Staggered GridView by detecting when the user has scrolled to the bottom:
@Composable
fun InfiniteScrollStaggeredGrid(
items: List<GridItem>,
onLoadMore: () -> Unit
) {
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(2),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalItemSpacing = 16.dp
) {
itemsIndexed(items) { index, item ->
// Your item content here
StaggeredGridItem(item = item, onClick = {})
// Check if we've reached the last item
if (index == items.size - 1) {
LaunchedEffect(Unit) {
onLoadMore()
}
}
}
}
}
This implementation will call the onLoadMore
function when the user scrolls to the last item, allowing you to load additional content.
You can enhance your Jetpack Compose Staggered GridView by animating items as they appear:
@Composable
fun AnimatedStaggeredGridItem(
item: GridItem,
onClick: () -> Unit,
index: Int
) {
var visible by remember { mutableStateOf(false) }
LaunchedEffect(key1 = Unit) {
delay(index * 50L) // Stagger the animation based on item position
visible = true
}
val alpha by animateFloatAsState(
targetValue = if (visible) 1f else 0f,
animationSpec = tween(durationMillis = 300)
)
val offset by animateDpAsState(
targetValue = if (visible) 0.dp else 20.dp,
animationSpec = tween(durationMillis = 300)
)
Card(
modifier = Modifier
.fillMaxWidth()
.offset(y = offset)
.alpha(alpha)
.clickable(onClick = onClick),
elevation = CardDefaults.cardElevation(4.dp)
) {
// Item content
}
}
By using this approach in your Jetpack Compose Staggered GridView, each item will fade in and slide up with a slight delay, creating a pleasing cascading effect.
A real-world Jetpack Compose Staggered GridView implementation often needs to display data from external sources like a network API or local database.
You can enhance your Jetpack Compose Staggered GridView by loading images from the internet using a library like Coil:
// Add this to your app's build.gradle
// implementation "io.coil-kt:coil-compose:2.2.2"
import coil.compose.AsyncImage
@Composable
fun NetworkImageStaggeredGridItem(
item: NetworkGridItem,
onClick: () -> Unit
) {
Card(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick),
elevation = CardDefaults.cardElevation(4.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = item.title,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 8.dp)
)
Text(
text = item.description,
style = MaterialTheme.typography.bodyMedium
)
item.imageUrl?.let { imageUrl ->
Spacer(modifier = Modifier.height(8.dp))
AsyncImage(
model = imageUrl,
contentDescription = item.title,
modifier = Modifier
.fillMaxWidth()
.height(120.dp),
contentScale = ContentScale.Crop
)
}
}
}
}
data class NetworkGridItem(
val title: String,
val description: String,
val imageUrl: String? = null
)
This allows your Jetpack Compose Staggered GridView to efficiently load and display images from network sources.
The Jetpack Compose Staggered GridView provides a flexible and powerful way to create dynamic, Pinterest-style layouts in your Android applications. By leveraging the LazyVerticalStaggeredGrid composable and understanding its key properties, you can create visually appealing grids that efficiently display content of varying heights.
Whether you're building a photo gallery, product catalog, or content feed, the Jetpack Compose Staggered GridView offers the flexibility and performance needed for modern Android applications. With the techniques and examples provided in this guide, you now have the knowledge to implement advanced staggered grid layouts in your Jetpack Compose applications.