Jetpack Compose LazyColumn is a vertically scrolling list component that efficiently renders only the items currently visible on screen. Unlike the traditional RecyclerView, Jetpack Compose LazyColumn simplifies list creation with a declarative approach, eliminating the need for adapters and view holders. The Jetpack Compose LazyColumn component is designed to handle large datasets efficiently while providing smooth scrolling performance.
Jetpack Compose LazyColumn integrates seamlessly with Compose's state management system. Here's a quick example:
val listItems = remember { mutableStateOf(List(100) { "Item $it" }) }
LazyColumn {
items(listItems.value) { item ->
Text(
text = item,
modifier = Modifier.padding(16.dp)
)
}
}
The LazyColumn automatically updates when the list state changes, providing reactive UI updates without manual intervention.
Jetpack Compose LazyColumn allows you to add padding around the entire content using the contentPadding
parameter:
LazyColumn(
contentPadding = PaddingValues(
start = 16.dp,
top = 8.dp,
end = 16.dp,
bottom = 8.dp
)
) {
items(100) { index ->
Text("Item #$index", Modifier.padding(8.dp))
}
}
This padding applies to the entire list content rather than individual items, making it perfect for accommodating system insets or design requirements.
The Jetpack Compose LazyColumn provides multiple ways to add spacing between items:
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(100) { index ->
Text("Item #$index")
}
}
The verticalArrangement
parameter with Arrangement.spacedBy()
creates consistent spacing between all items in the LazyColumn.
Jetpack Compose LazyColumn gives you fine-grained control over scrolling behavior:
val lazyListState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
LazyColumn(state = lazyListState) {
items(100) { index ->
Text("Item #$index", Modifier.padding(16.dp))
}
}
Button(
onClick = {
coroutineScope.launch {
// Scroll to item at position 10
lazyListState.animateScrollToItem(10)
}
}
) {
Text("Scroll to item #10")
}
The rememberLazyListState()
function creates a state object that tracks scrolling position and provides methods for programmatic scrolling.
Jetpack Compose LazyColumn supports item keys for efficient updates and animations:
data class Person(val id: Int, val name: String)
val people = List(100) { Person(it, "Person $it") }
LazyColumn {
items(
items = people,
key = { person -> person.id }
) { person ->
Text(person.name, Modifier.padding(16.dp))
}
}
Providing a key function helps Jetpack Compose LazyColumn identify which items have changed, moved, or been removed, enabling more efficient updates and animations.
Jetpack Compose LazyColumn supports sticky headers that remain at the top during scrolling:
LazyColumn {
stickyHeader {
Text(
"Sticky Header",
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.primary)
.padding(16.dp),
color = MaterialTheme.colorScheme.onPrimary
)
}
items(100) { index ->
Text("Item #$index", Modifier.padding(16.dp))
}
}
Sticky headers in Jetpack Compose LazyColumn are perfect for section titles or category headers that should remain visible while scrolling through a section.
Implement infinite scrolling in Jetpack Compose LazyColumn with the onReachEnd
callback:
val lazyListState = rememberLazyListState()
val isLoading = remember { mutableStateOf(false) }
val items = remember { mutableStateOf(List(20) { "Initial Item $it" }) }
LaunchedEffect(lazyListState) {
snapshotFlow { lazyListState.layoutInfo.visibleItemsInfo }
.collect { visibleItems ->
// Check if last item is visible
if (!isLoading.value &&
visibleItems.any { it.index == items.value.size - 1 }) {
isLoading.value = true
// Simulate loading more items
delay(1000)
val newItems = List(10) { "New Item ${items.value.size + it}" }
items.value = items.value + newItems
isLoading.value = false
}
}
}
LazyColumn(state = lazyListState) {
items(items.value) { item ->
Text(item, Modifier.padding(16.dp))
}
if (isLoading.value) {
item {
CircularProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.wrapContentWidth(Alignment.CenterHorizontally)
)
}
}
}
This pattern allows Jetpack Compose LazyColumn to load additional items when the user scrolls near the end of the list, providing a seamless infinite scrolling experience.
Here's a full implementation of a Jetpack Compose LazyColumn with various features:
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch
@Composable
fun JetpackComposeLazyColumnDemo() {
val coroutineScope = rememberCoroutineScope()
val lazyListState = rememberLazyListState()
// Sample data
data class Contact(val id: Int, val name: String, val phone: String)
val alphabet = ('A'..'Z').toList()
val contacts = remember {
alphabet.flatMap { letter ->
List(5) { index ->
Contact(
id = letter.code * 100 + index,
name = "$letter Name $index",
phone = "555-${letter.code}-${1000 + index}"
)
}
}.sortedBy { it.name }
}
// Group contacts by first letter
val groupedContacts = contacts.groupBy { it.name.first() }
// Selected contact state
var selectedContact by remember { mutableStateOf<Contact?>(null) }
Column(modifier = Modifier.fillMaxSize()) {
// Show selected contact details
selectedContact?.let { contact ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = contact.name,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
Text(text = "Phone: ${contact.phone}")
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = { selectedContact = null },
modifier = Modifier.align(Alignment.End)
) {
Text("Close")
}
}
}
}
// Quick scroll buttons
Row(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(rememberScrollState())
.padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
alphabet.forEach { letter ->
if (groupedContacts.containsKey(letter)) {
Button(
onClick = {
coroutineScope.launch {
// Find the position of the first item that starts with this letter
val position = contacts.indexOfFirst { it.name.startsWith(letter) }
if (position >= 0) {
lazyListState.animateScrollToItem(
index = position + groupedContacts.keys
.filter { it < letter }
.count() // Add the number of headers before this section
)
}
}
},
modifier = Modifier.size(40.dp),
contentPadding = PaddingValues(0.dp)
) {
Text(letter.toString())
}
}
}
}
// LazyColumn with contacts
LazyColumn(
state = lazyListState,
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = 16.dp),
verticalArrangement = Arrangement.spacedBy(1.dp)
) {
groupedContacts.forEach { (letter, contactsInGroup) ->
stickyHeader {
Box(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.primaryContainer)
.padding(8.dp)
) {
Text(
text = letter.toString(),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onPrimaryContainer,
fontWeight = FontWeight.Bold
)
}
}
items(
items = contactsInGroup,
key = { it.id }
) { contact ->
ListItem(
headlineContent = { Text(contact.name) },
supportingContent = { Text(contact.phone) },
modifier = Modifier
.clickable { selectedContact = contact }
.padding(horizontal = 16.dp, vertical = 8.dp)
)
Divider(color = Color.LightGray, thickness = 0.5.dp)
}
}
}
}
}
// Add this to your app
@Composable
fun MyApp() {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
JetpackComposeLazyColumnDemo()
}
}
}
This complete example demonstrates a contacts app using Jetpack Compose LazyColumn with:
To use this code, you'll need the following dependencies in your build.gradle
file:
dependencies {
implementation "androidx.compose.ui:ui:1.5.0"
implementation "androidx.compose.material3:material3:1.1.0"
implementation "androidx.compose.foundation:foundation:1.5.0"
implementation "androidx.compose.runtime:runtime:1.5.0"
implementation "androidx.activity:activity-compose:1.7.0"
}
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Star
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.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch
@Composable
fun LazyColumnExample() {
// Create some sample data
data class Item(
val id: Int,
val title: String,
val description: String,
var isFavorite: Boolean = false
)
val items = remember {
mutableStateListOf<Item>().apply {
repeat(50) { index ->
add(
Item(
id = index,
title = "Item $index",
description = "This is a detailed description for item $index. " +
"It contains multiple lines of text to demonstrate how LazyColumn " +
"handles multiline content in each item.",
isFavorite = index % 5 == 0
)
)
}
}
}
// LazyListState to control scrolling behavior
val lazyListState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
// Track selected item
var selectedItemId by remember { mutableStateOf<Int?>(null) }
Column(modifier = Modifier.fillMaxSize()) {
// Header section with scroll controls
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.primaryContainer)
.padding(16.dp)
) {
Text(
text = "Jetpack Compose LazyColumn Demo",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onPrimaryContainer
)
Spacer(modifier = Modifier.height(8.dp))
// Scroll control buttons
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Button(
onClick = {
coroutineScope.launch {
// Scroll to top
lazyListState.animateScrollToItem(0)
}
}
) {
Text("Scroll to Top")
}
Button(
onClick = {
coroutineScope.launch {
// Scroll to middle
lazyListState.animateScrollToItem(items.size / 2)
}
}
) {
Text("Scroll to Middle")
}
Button(
onClick = {
coroutineScope.launch {
// Scroll to bottom
lazyListState.animateScrollToItem(items.size - 1)
}
}
) {
Text("Scroll to Bottom")
}
}
}
// Selected item details
selectedItemId?.let { id ->
val selectedItem = items.find { it.id == id }
selectedItem?.let {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = it.title,
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
Text(text = it.description)
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
Button(
onClick = { selectedItemId = null }
) {
Text("Close")
}
}
}
}
}
}
// Main LazyColumn with different properties demonstrated
LazyColumn(
state = lazyListState,
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxSize()
) {
// Header item
item {
Text(
text = "Header",
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.secondaryContainer)
.padding(16.dp),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSecondaryContainer
)
}
// Sticky header
stickyHeader {
Surface(
modifier = Modifier.fillMaxWidth(),
color = MaterialTheme.colorScheme.primary
) {
Text(
text = "Sticky Header - Always Visible While Scrolling",
modifier = Modifier.padding(16.dp),
color = MaterialTheme.colorScheme.onPrimary,
fontWeight = FontWeight.Bold
)
}
}
// Items with index
itemsIndexed(
items = items,
key = { _, item -> item.id } // Using key for better performance and animations
) { index, item ->
Card(
modifier = Modifier
.fillMaxWidth()
.clickable { selectedItemId = item.id },
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Position $index: ${item.title}",
fontWeight = FontWeight.Bold,
fontSize = 16.sp
)
IconButton(
onClick = {
items[index] = item.copy(isFavorite = !item.isFavorite)
}
) {
Icon(
imageVector = if (item.isFavorite) Icons.Filled.Favorite else Icons.Filled.Star,
contentDescription = "Toggle Favorite",
tint = if (item.isFavorite) Color.Red else Color.Gray
)
}
}
Spacer(modifier = Modifier.height(4.dp))
Text(
text = item.description,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
}
}
}
// Footer item
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "End of List - ${items.size} items total",
color = Color.Gray
)
}
}
}
}
}
// For use in a standard Compose app:
@Composable
fun LazyColumnApp() {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
LazyColumnExample()
}
}
}
// Required dependencies in build.gradle:
implementation "androidx.compose.ui:ui:1.5.4"
implementation "androidx.compose.material3:material3:1.1.2"
implementation "androidx.compose.foundation:foundation:1.5.4"
implementation "androidx.compose.material:material-icons-extended:1.5.4"
implementation "androidx.activity:activity-compose:1.8.1"
For the latest Jetpack Compose versions and documentation, visit the official Jetpack Compose website.
By implementing Jetpack Compose LazyColumn in your applications, you can create efficient, responsive lists with significantly less code than traditional RecyclerView implementations. The declarative nature of Jetpack Compose LazyColumn makes it easier to understand, maintain, and modify your list implementations while providing excellent performance for your Android applications.