Jetpack Compose LazyRow is a composable function in Jetpack Compose that creates a horizontally scrollable list. Unlike traditional RecyclerView implementations, Jetpack Compose LazyRow only composes and lays out the items that are visible in the viewport, making it highly efficient for long lists. The Jetpack Compose LazyRow component is part of the androidx.compose.foundation.lazy package and follows the declarative UI paradigm of Jetpack Compose.
Jetpack Compose LazyRow efficiently manages state changes and recompositions. Here's a simple example of how state works with LazyRow:
val items = remember { mutableStateOf(listOf("Item 1", "Item 2", "Item 3")) }
LazyRow {
items(items.value) { item ->
Text(text = item, modifier = Modifier.padding(8.dp))
}
}
In this example, whenever the items
state changes, only the affected items in the Jetpack Compose LazyRow will be recomposed, not the entire list.
You can add padding around the content of your Jetpack Compose LazyRow using the contentPadding
parameter:
LazyRow(
contentPadding = PaddingValues(
start = 16.dp,
top = 8.dp,
end = 16.dp,
bottom = 8.dp
)
) {
// Items here
}
This adds padding around the entire content of the Jetpack Compose LazyRow, which is particularly useful for ensuring items aren't clipped at the edges of the screen.
The horizontalArrangement
parameter allows you to control how items are spaced within the Jetpack Compose LazyRow:
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// Items here
}
Other arrangement options include Arrangement.Start
, Arrangement.End
, Arrangement.Center
, and Arrangement.SpaceBetween
, giving you fine-grained control over item positioning in your Jetpack Compose LazyRow.
For lists with multiple item types, Jetpack Compose LazyRow provides support through content types:
LazyRow {
itemsIndexed(
items = mixedItems,
contentType = { _, item ->
when(item) {
is TextItem -> "text"
is ImageItem -> "image"
}
}
) { index, item ->
when(item) {
is TextItem -> Text(text = item.text)
is ImageItem -> Image(
painter = painterResource(id = item.resId),
contentDescription = null
)
}
}
}
This optimization helps Jetpack Compose LazyRow to better reuse composables of the same type, improving performance.
Jetpack Compose LazyRow supports animations when items are added, removed, or reordered:
LazyRow(
modifier = Modifier.animateItemPlacement(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
) {
// Items here
}
This creates a smooth animation effect when the items in your Jetpack Compose LazyRow change position.
Although less common in horizontal lists, Jetpack Compose LazyRow supports sticky headers:
LazyRow {
stickyHeader {
Text(
text = "Sticky Header",
modifier = Modifier
.background(MaterialTheme.colorScheme.primary)
.padding(16.dp)
.fillMaxHeight(),
color = MaterialTheme.colorScheme.onPrimary
)
}
items(50) { index ->
Text(
text = "Item $index",
modifier = Modifier.padding(16.dp)
)
}
}
This keeps the header visible at the start of the Jetpack Compose LazyRow as the user scrolls through the content.
Implementing infinite scrolling or pagination with Jetpack Compose LazyRow is straightforward:
LazyRow {
items(currentItems) { item ->
ItemCard(item)
}
item {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.padding(16.dp)
)
} else if (hasMoreItems) {
Button(
onClick = { loadMoreItems() },
modifier = Modifier.padding(16.dp)
) {
Text("Load More")
}
}
}
}
This approach allows your Jetpack Compose LazyRow to load more items as the user reaches the end of the list.
Let's put everything together in a full example that demonstrates key features of Jetpack Compose LazyRow:
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
data class Product(
val id: Int,
val name: String,
val price: String,
val imageRes: Int
)
@Composable
fun ProductScreen() {
val products = listOf(
Product(1, "Laptop", "$999.99", R.drawable.laptop),
Product(2, "Smartphone", "$699.99", R.drawable.smartphone),
Product(3, "Headphones", "$149.99", R.drawable.headphones),
Product(4, "Tablet", "$349.99", R.drawable.tablet),
Product(5, "Smartwatch", "$249.99", R.drawable.smartwatch),
Product(6, "Camera", "$599.99", R.drawable.camera)
)
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = "Featured Products",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = 16.dp)
)
LazyRow(
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
items(products) { product ->
ProductCard(product)
}
}
}
}
}
@Composable
fun ProductCard(product: Product) {
Card(
modifier = Modifier
.width(180.dp)
.height(240.dp),
shape = RoundedCornerShape(12.dp)
) {
Column {
Image(
painter = painterResource(id = product.imageRes),
contentDescription = product.name,
modifier = Modifier
.height(120.dp)
.fillMaxWidth()
.clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)),
contentScale = ContentScale.Crop
)
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = product.name,
fontWeight = FontWeight.Bold,
fontSize = 16.sp
)
Text(
text = product.price,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Medium
)
Box(
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.primaryContainer,
shape = RoundedCornerShape(4.dp)
)
.padding(horizontal = 8.dp, vertical = 4.dp)
) {
Text(
text = "New",
color = MaterialTheme.colorScheme.onPrimaryContainer,
fontSize = 12.sp
)
}
}
}
}
}
This comprehensive example showcases a products catalog using Jetpack Compose LazyRow. The code demonstrates content padding, horizontal arrangement, and custom item rendering within the Jetpack Compose LazyRow.
Jetpack Compose LazyRow can be enhanced with various modifiers and composables. For example, you can implement snap scrolling:
val state = rememberLazyListState()
val snappedItemIndex = remember { mutableStateOf(0) }
LazyRow(
state = state,
flingBehavior = rememberSnapFlingBehavior(lazyListState = state),
modifier = Modifier.fillMaxWidth()
) {
// Items here
}
LaunchedEffect(state.isScrollInProgress) {
if (!state.isScrollInProgress) {
snappedItemIndex.value = state.firstVisibleItemIndex
}
}
This creates a snapping effect as the user scrolls through items in the Jetpack Compose LazyRow, enhancing the user experience.
The Jetpack Compose LazyRow component is an essential tool for Android developers working with horizontal lists in their applications. By leveraging the features and properties discussed in this guide, you can create efficient, responsive, and visually appealing scrollable horizontal lists using Jetpack Compose LazyRow.