Staggered Grid View has been seen in most applications such as Pinterest in which each item of gridview takes its own height and aligns within the grid view according to that. In this article, we will look at how to implement Staggered Grid View in Android using Jetpack Compose
Step by Step Implementation
Step 1 : Create a New Project in Android Studio
To create a new project in the Android Studio, please refer to How to Create a new Project in Android Studio with Jetpack Compose.
Note: Select Kotlin as the programming language.
Step 2 : Adding images to the drawable folder
Copy all the images which you want to add to your grid view. Navigate to app > res > drawable. Right-click on it and paste all the images into the drawable folder.
Step 3 : Working with the MainActivity.kt file
Go to the MainActivity.kt file and refer to the following code. Below is the code for the MainActivity.kt file. Comments are added inside the code to understand the code in more detail.
MainActivity.kt:
package com.geeksforgeeks.demo
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.*
import com.geeksforgeeks.demo.ui.theme.DemoTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
DemoTheme(dynamicColor = false, darkTheme = false) {
Surface(
modifier = Modifier.fillMaxSize(),
color = Color.White
) {
StaggeredGridView()
}
}
}
}
}
@Composable
fun StaggeredGridView() {
// list of images
val images = listOf(
R.drawable.k1, R.drawable.k2, R.drawable.k3,
R.drawable.k4, R.drawable.k5, R.drawable.k6,
R.drawable.k7, R.drawable.k8, R.drawable.k9,
R.drawable.k10, R.drawable.k11, R.drawable.k12
)
// creating a column for the entire grid layout
Column(
modifier = Modifier
.fillMaxSize()
.padding(5.dp)
.verticalScroll(rememberScrollState())
) {
// custom view with 2 columns
CustomStaggeredVerticalGrid(numColumns = 2, modifier = Modifier.padding(5.dp)) {
images.forEach { img ->
// creating a design for each item
Card(
modifier = Modifier
.fillMaxWidth()
.padding(5.dp),
// for shadows
elevation = CardDefaults.cardElevation(10.dp),
// for rounded corners
shape = RoundedCornerShape(10.dp)
) {
// column to align each item
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// adding an image to each item
Image(
painter = painterResource(id = img),
contentDescription = "image",
alignment = Alignment.Center
)
}
}
}
}
}
}
// Custom composable for a staggered grid view
@Composable
fun CustomStaggeredVerticalGrid(
modifier: Modifier = Modifier,
numColumns: Int = 2,
content: @Composable () -> Unit
) {
Layout(
content = content,
modifier = modifier
) { measurables, constraints ->
// Calculate column width based on available space
val columnWidth = constraints.maxWidth / numColumns
// Constraints to ensure items fit
// within each column's width
val itemConstraints = constraints.copy(maxWidth = columnWidth)
// Array to track the current height
// of each column (to determine where
// to place the next item)
val columnHeights = IntArray(numColumns) { 0 }
// Measure all child composables and
// assign them to the shortest column
val placeables = measurables.map { measurable ->
// Find the column with the least height
val column = findShortestColumn(columnHeights)
// Measure item with column constraints
val placeable = measurable.measure(itemConstraints)
// Update the height of the chosen column
columnHeights[column] += placeable.height
placeable
}
// Determine total layout height (max column height)
// while respecting constraints
val height = columnHeights.maxOrNull()?.coerceIn(constraints.minHeight, constraints.maxHeight) ?: constraints.minHeight
layout(width = constraints.maxWidth, height = height) {
// Track Y position for each column
val columnYPointers = IntArray(numColumns) { 0 }
placeables.forEach { placeable ->
// Find where to place the next item
val column = findShortestColumn(columnYPointers)
// Place item
placeable.place(x = columnWidth * column, y = columnYPointers[column])
// Update Y pointer for the column
columnYPointers[column] += placeable.height
}
}
}
}
// Finds the column with the least
// height to place the next item
private fun findShortestColumn(columnHeights: IntArray): Int {
return columnHeights.indices.minByOrNull { columnHeights[it] } ?: 0
}