Jetpack Compose Staggered GridView

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.

Key Properties of Jetpack Compose Staggered GridView

Let's explore the essential properties that you'll need to understand when working with a Jetpack Compose Staggered GridView:

1. Columns

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.

2. Content Padding

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.

3. Vertical Arrangement

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.

4. Horizontal Arrangement

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.

5. Item Content

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  
}  

Implementing a Jetpack Compose Staggered GridView

Now that we understand the key properties of a Jetpack Compose Staggered GridView, let's see how to implement one in your Android application.

Basic Implementation

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.

Dynamic Content Height

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.

Handling Click Events

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.

Complete Example of 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()  
        }  
    }  
}  

Advanced Techniques for Jetpack Compose Staggered GridView

Load More on Scroll

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.

Animated Item Appearance

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.

Integrating with Data Sources

A real-world Jetpack Compose Staggered GridView implementation often needs to display data from external sources like a network API or local database.

Loading Images from Network

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.

Summary

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.