Lazy Staggered Grid Layouts in Jetpack Compose

Jetpack Compose Lazy Staggered Grid Layouts

In this article, we’ll learn how to implement lazy staggered grid layouts in Android Jetpack Compose.

Prerequisites:

What are Lazy Staggered Grid Layouts in Jetpack Compose?

Lazy staggered APIs display items in a grid layout and allow each item to have a different height or width.

One example of a lazy staggered API is the Pinterest app. The app shows images with different heights in a vertically scrollable container.

For this article, create an empty Jetpack Compose project and open MainActivity.kt. Create a composable called MyUI() and call it from the onCreate().

// we need the following packages
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.staggeredgrid.LazyHorizontalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlin.random.Random

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            YourProjectNameTheme(darkTheme = false) {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    Column(
                        modifier = Modifier.fillMaxSize()
                    ) {
                        MyUI()
                    }
                }
            }
        }
    }
}

@Composable
fun MyUI() {
   
}

We’ll write our code in the MyUI().

There are two lazy staggered grid APIs in Jetpack Compose:

  1. LazyVerticalStaggeredGrid
  2. LazyHorizontalStaggeredGrid

Note: As of Compose 1.4.3, these APIs are experimental.

1. Lazy Vertical Staggered Grid:

It displays a vertically scrollable grid. The items can have different heights. The API looks like this:

@ExperimentalFoundationApi
@Composable
fun LazyVerticalStaggeredGrid(
    columns: StaggeredGridCells,
    modifier: Modifier = Modifier,
    state: LazyStaggeredGridState = rememberLazyStaggeredGridState(),
    contentPadding: PaddingValues = PaddingValues(0.dp),
    reverseLayout: Boolean = false,
    verticalItemSpacing: Dp = 0.dp,
    horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(0.dp),
    flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
    userScrollEnabled: Boolean = true,
    content: LazyStaggeredGridScope.() -> Unit
)

The columns parameter decides the number of columns. There are two options – Fixed and Adaptive. To learn about them, read Lazy Grid Layouts article.

Simple Example:

Let’s create the following output:

Lazy Vertical Staggered Grid Example

Each box has a random color and height. So, first, create a data class.

data class ListItem(
    val height: Dp,
    val color: Color
)

We can generate random values using Kotlin’s Random package:

val items = (1..100).map {
    ListItem(
        // random height
        height = Random.nextInt(100, 300).dp,
        // random color
        color = Color(
            red = Random.nextInt(255),
            green = Random.nextInt(255),
            blue = Random.nextInt(255),
            alpha = 255
        )
    )
}

We can add these items using the itemsIndexed() method:

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MyUI() {

    val items = (1..100).map {
        ListItem(
            // random height
            height = Random.nextInt(100, 300).dp,
            // random color
            color = Color(
                red = Random.nextInt(255),
                green = Random.nextInt(255),
                blue = Random.nextInt(255),
                alpha = 255
            )
        )
    }

    LazyVerticalStaggeredGrid(
        columns = StaggeredGridCells.Fixed(count = 3), // 3 columns
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(all = 12.dp),
        horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
        verticalItemSpacing = 8.dp
    ) {
        itemsIndexed(items) { index, item ->
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(height = item.height)
                    .background(color = item.color, shape = RoundedCornerShape(size = 10.dp)),
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = "$index",
                    color = Color.White,
                    fontWeight = FontWeight.Bold
                )
            }
        }
    }
}

data class ListItem(
    val height: Dp,
    val color: Color
)

Output:

Lazy Vertical Staggered Grid Example

Adaptive Staggered Grid:

Instead of a fixed size, we can create a grid with an adaptive number of columns.

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MyUI() {

    val items = (1..100).map {
        ListItem(
            // random height
            height = Random.nextInt(100, 300).dp,
            // random color
            color = Color(
                red = Random.nextInt(255),
                green = Random.nextInt(255),
                blue = Random.nextInt(255),
                alpha = 255
            )
        )
    }

    LazyVerticalStaggeredGrid(
        columns = StaggeredGridCells.Adaptive(minSize = 80.dp), // adaptive
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(all = 12.dp),
        horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
        verticalItemSpacing = 8.dp
    ) {
        itemsIndexed(items) { index, item ->
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(height = item.height)
                    .background(color = item.color, shape = RoundedCornerShape(size = 10.dp)),
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = "$index",
                    color = Color.White,
                    fontWeight = FontWeight.Bold
                )
            }
        }
    }
}

data class ListItem(
    val height: Dp,
    val color: Color
)

Output:

Adaptive Grid

2. Lazy Horizontal Staggered Grid:

It displays a horizontally scrollable grid. The items can have different widths. The API looks similar to the staggered vertical grid:

@ExperimentalFoundationApi
@Composable
fun LazyHorizontalStaggeredGrid(
    rows: StaggeredGridCells,
    modifier: Modifier = Modifier,
    state: LazyStaggeredGridState = rememberLazyStaggeredGridState(),
    contentPadding: PaddingValues = PaddingValues(0.dp),
    reverseLayout: Boolean = false,
    verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(0.dp),
    horizontalItemSpacing: Dp = 0.dp,
    flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
    userScrollEnabled: Boolean = true,
    content: LazyStaggeredGridScope.() -> Unit
)

Simple Example:

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MyUI() {

    val items = (1..100).map {
        ListItem(
            // random width
            width = Random.nextInt(100, 300).dp,
            // random color
            color = Color(
                red = Random.nextInt(255),
                green = Random.nextInt(255),
                blue = Random.nextInt(255),
                alpha = 255
            )
        )
    }

    LazyHorizontalStaggeredGrid(
        rows = StaggeredGridCells.Fixed(count = 3),
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(all = 12.dp),
        verticalArrangement = Arrangement.spacedBy(space = 8.dp),
        horizontalItemSpacing = 8.dp
    ) {
        itemsIndexed(items) { index, item ->
            Box(
                modifier = Modifier
                    .fillMaxHeight()
                    .width(width = item.width)
                    .background(color = item.color, shape = RoundedCornerShape(size = 10.dp)),
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = "$index",
                    color = Color.White,
                    fontWeight = FontWeight.Bold
                )
            }
        }
    }
}

data class ListItem(
    val width: Dp,
    val color: Color
)

Output:

Lazy Horizontal Staggered Grid Example

Adaptive Staggered Grid:

We can set the adaptive size using the same StaggeredGridCells.Adaptive() API.

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MyUI() {

    val items = (1..100).map {
        ListItem(
            // random width
            width = Random.nextInt(100, 300).dp,
            // random color
            color = Color(
                red = Random.nextInt(255),
                green = Random.nextInt(255),
                blue = Random.nextInt(255),
                alpha = 255
            )
        )
    }

    LazyHorizontalStaggeredGrid(
        rows = StaggeredGridCells.Adaptive(minSize = 100.dp), // adaptive
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(all = 12.dp),
        verticalArrangement = Arrangement.spacedBy(space = 8.dp),
        horizontalItemSpacing = 8.dp
    ) {
        itemsIndexed(items) { index, item ->
            Box(
                modifier = Modifier
                    .fillMaxHeight()
                    .width(width = item.width)
                    .background(color = item.color, shape = RoundedCornerShape(size = 10.dp)),
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = "$index",
                    color = Color.White,
                    fontWeight = FontWeight.Bold
                )
            }
        }
    }
}

data class ListItem(
    val width: Dp,
    val color: Color
)

Output:

Adaptive Grid

This is all about lazy staggered grid layouts in Jetpack Compose. I hope you have learned something new. If you have any doubts, leave a comment below.

Related Articles:

References:

Leave a Comment