LazyColumn — komponen Jetpack Compose untuk menampilkan daftar yang dapat digulir dengan pemuatan malas (lazy loading). Tidak seperti RecyclerView, LazyColumn tidak membuat ViewHolder dan tidak menggunakan XML — semua elemen dijelaskan secara deklaratif melalui fungsi composable. Kami menunjukkan apa itu LazyColumn, bagaimana LazyListState dan items API bekerja, dan bagaimana mencapai performa 60 FPS. Di IT Sectr kami menggunakan LazyColumn di semua proyek Compose baru. Untuk perbandingan dengan pendekatan klasik, baca artikel tentang RecyclerView.
Poin Utama
items(), itemsIndexed(), items() dengan kunci untuk pengikatan data.LazyColumn — adalah fungsi composable dari pustaka Foundation Compose yang menampilkan daftar vertikal elemen dengan pemuatan malas (lazy): hanya elemen yang terlihat di layar atau berada di dekatnya (zona prefetch) yang dibuat dan dikomposisi. LazyColumn muncul di Compose 1.0 (2021) bersama dengan LazyRow (daftar horizontal) dan LazyVerticalGrid (grid).
Pemuatan malas bekerja melalui mekanisme SubcomposeLayout: LazyColumn mengukur ruang yang tersedia, meminta rentang terlihat dari LayoutInfo dan hanya mengomposisi elemen dalam rentang tersebut. Elemen yang telah keluar dari layar meninggalkan komposisi (kecuali buffer prefetch). Menurut data Google (Android Performance, 2026), LazyColumn memberikan 60 FPS saat menggulir daftar 10000+ elemen pada perangkat kelas menengah.
Column dengan gulir vertikal — adalah penempatan biasa semua elemen satu di bawah yang lain dengan menerapkan pengubah verticalScroll. Column tidak malas: semua elemen anak dikomposisi segera, bahkan jika tidak terlihat. Untuk daftar 200+ elemen, Column menyebabkan lag signifikan saat startup. LazyColumn hanya mengomposisi elemen terlihat + buffer (secara default 1 layar ke depan/belakang), membuatnya sangat diperlukan untuk daftar panjang.
items API — kumpulan fungsi ekstensi untuk LazyColumn yang menerima daftar data dan deskriptor DSL untuk setiap elemen. Fungsi utama: items(count, key, itemContent) untuk jumlah tetap, items(list, key, itemContent) untuk daftar, itemsIndexed(list, key, itemContent) dengan indeks. Parameter key — kondisi wajib untuk performa — memberikan Compose pengidentifikasi elemen yang stabil.
// LazyColumn dasar dengan itemContent
@Composable
fun ArticleList(articles: List<Article>) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// Header — tidak malas, selalu di awal
item {
Text("Artikel terbaru", style = MaterialTheme.typography.headlineMedium)
}
// Daftar artikel dengan kunci berdasarkan id
items(articles, key = { it.id }) { article ->
ArticleCard(
title = article.title,
summary = article.summary,
onClick = { onArticleClick(article.id) }
)
}
// Footer dengan indikator pemuatan
item {
CircularProgressIndicator(
modifier = Modifier.fillMaxWidth().padding(16.dp)
)
}
}
}
// Elemen daftar kustom
@Composable
fun ArticleCard(title: String, summary: String, onClick: () -> Unit) {
Card(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
) {
Column(modifier = Modifier.padding(12.dp)) {
Text(text = title, style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.height(4.dp))
Text(text = summary, style = MaterialTheme.typography.bodyMedium)
}
}
}
item {} vs items(): gunakan item {} untuk elemen tunggal yang selalu ada dalam daftar (header, footer, pemisah). Gunakan items() untuk daftar data dinamis. items() menerima Iterable atau Int dan blok DSL yang dipanggil untuk setiap elemen. Gabungkan item dan items dalam satu LazyColumn: urutan panggilan menentukan urutan tampilan.
LazyListState — objek yang menyimpan status daftar: posisi gulir saat ini (firstVisibleItemIndex, firstVisibleItemScrollOffset), elemen terlihat (layoutInfo) dan metode gulir terprogram (scrollToItem, animateScrollToItem). LazyListState dibuat melalui rememberLazyListState() dan diteruskan ke LazyColumn melalui parameter state.
// LazyColumn dengan mempertahankan posisi gulir
@Composable
fun ScrollingList(items: List<Item>) {
val listState = rememberLazyListState()
Box(modifier = Modifier.fillMaxSize()) {
LazyColumn(state = listState) {
items(items, key = { it.id }) { item ->
ListItemView(item = item)
}
}
// Tombol "Ke atas" muncul setelah elemen ke-10
val showButton by remember {
derivedStateOf { listState.firstVisibleItemIndex > 10 }
}
AnimatedVisibility(visible = showButton) {
FloatingActionButton(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(16.dp),
onClick = {
coroutineScope.launch {
listState.animateScrollToItem(0)
}
}
) {
Icon(Icons.Default.KeyboardArrowUp, "Naik")
}
}
}
}
// Mendapatkan informasi tentang elemen yang terlihat
@Composable
fun ListDebugInfo(listState: LazyListState) {
val visibleItems = listState.layoutInfo.visibleItemsInfo
val totalItems = listState.layoutInfo.totalItemsCount
val firstItem = visibleItems.firstOrNull()?.index
val lastItem = visibleItems.lastOrNull()?.index
Text("Menampilkan $firstItem–$lastItem dari $totalItems")
}
Gulir terprogram dilakukan melalui fungsi suspend listState: scrollToItem(index, scrollOffset) — perpindahan instan (tanpa animasi), animateScrollToItem(index) — dengan animasi. Untuk menggulir dari korutin gunakan LaunchedEffect atau coroutineScope.launch. Penting: jangan panggil fungsi suspend dari komposisi (di dalam @Composable) — gunakan lambda (onClick, LaunchedEffect).
LazyColumn dan RecyclerView menyelesaikan tugas yang sama — menampilkan daftar panjang secara efisien. Perbedaannya terletak pada arsitektur: RecyclerView menggunakan ViewHolder dan Adapter (Android View klasik), LazyColumn — fungsi composable deklaratif tanpa XML dan ViewHolder. Pilihan tergantung pada tumpukan teknologi proyek: Compose vs View-based UI.
| Parameter | LazyColumn (Compose) | RecyclerView (View) |
|---|---|---|
| Tata letak | Fungsi Composable (Kotlin DSL) | File XML + ViewBinding |
| Adapter | items() / itemsIndexed() DSL | RecyclerView.Adapter + ViewHolder |
| Pengurutan/penyaringan | Snapshot state + derivedStateOf | DiffUtil + AsyncListDiffer |
| Animasi | AnimatedVisibility + Modifier.animateItem | DefaultItemAnimator |
| Tipe elemen berbeda | item {}/items() + when berdasarkan tipe | getItemViewType + beberapa ViewHolder |
| Gulir ke posisi | listState.animateScrollToItem() | layoutManager.scrollToPosition() |
| Prefetch | In-process (buffer SubcomposeLayout) | RecycledViewPool + GapWorker |
| Jaringan/paginasi | collectAsLazyPagingItems() + Paging 3 | PagingDataAdapter + Paging 3 |
Rekomendasi Google (Android Developers, 2026): untuk proyek baru di Jetpack Compose gunakan LazyColumn. Untuk proyek View-based gunakan RecyclerView. LazyColumn membutuhkan Compose, yang menambah sekitar 3–5 MB ke APK — pertimbangkan ini saat mendukung perangkat lama. Jika proyek sudah menggunakan sistem View, LazyColumn tidak dapat hidup berdampingan di layar yang sama dengan tata letak XML tanpa AndroidView — dalam kasus seperti itu lebih mudah tetap menggunakan RecyclerView.
LazyVerticalGrid — versi LazyColumn untuk grid dengan jumlah kolom tetap (columns). LazyHorizontalGrid — grid horizontal dengan jumlah baris tetap (rows). Kedua komponen menggunakan prinsip pemuatan malas yang sama dan items API yang sama dengan LazyColumn. Parameter columns menentukan jumlah kolom melalui GridCells.Fixed(N) atau GridCells.Adaptive(minSize).
// Grid dengan kolom adaptif
@Composable
fun PhotoGrid(photos: List<Photo>) {
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 128.dp),
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(photos, key = { it.id }) { photo ->
AsyncImage(
model = photo.url,
contentDescription = photo.title,
modifier = Modifier.aspectRatio(1f)
)
}
}
}
// Daftar horizontal (LazyRow)
@Composable
fun CategoryCarousel(categories: List<Category>) {
LazyRow(
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
items(categories, key = { it.id }) { category ->
FilterChip(
selected = category.isSelected,
onClick = { onCategoryClick(category.id) },
label = { Text(category.name) }
)
}
}
}
GridCells.Adaptive secara otomatis menghitung jumlah kolom berdasarkan ukuran minimum sel. Misalnya, GridCells.Adaptive(128.dp) pada layar selebar 360 dp menempatkan 2 kolom, pada tablet 600 dp — 4 kolom. GridCells.Fixed(2) selalu menampilkan tepat 2 kolom. Adaptive lebih disukai untuk adaptasi multi-layar — secara otomatis menyesuaikan dengan lebar layar tanpa kueri media.
Performa LazyColumn tergantung pada tiga faktor: stabilitas kunci (key), jenis konten (contentType) dan buffer prefetch (beyondBounds). Secara default, LazyColumn mem-buffer 1 layar ke depan dan 1 ke belakang. Google (Android Developers, 2026) merekomendasikan mengonfigurasi parameter ini untuk daftar dengan tipe elemen berbeda atau komposisi kompleks.
| Parameter | Deskripsi | Rekomendasi |
|---|---|---|
| key | Pengidentifikasi elemen yang stabil | Selalu tentukan key = { it.id }. Tanpa key, Compose menggunakan indeks, dan pengaturan ulang elemen merusak animasi. |
| contentType | Jenis konten untuk pemisahan kumpulan | Tentukan untuk daftar dengan tipe elemen berbeda (teks + gambar + iklan). Compose akan menggunakan kembali komposisi hanya dalam contentType yang sama. |
| beyondBounds | Jumlah layar untuk prefetch | Tingkatkan ke 2–3 untuk daftar dengan gambar berat. Default 1 layar. |
| Modifier.animateItem | Animasi perubahan posisi | Gunakan untuk pengurutan dan penyaringan animasi (Compose 1.7+). |
// LazyColumn dioptimalkan dengan contentType dan prefetch
@Composable
fun OptimizedFeed(items: List<FeedItem>) {
val listState = rememberLazyListState()
LazyColumn(
state = listState,
modifier = Modifier.fillMaxSize(),
beyondBoundsPageCount = 2 // prefetch 2 layar
) {
items(
items = items,
key = { it.id },
contentType = { it.type } // pemisahan berdasarkan tipe
) { item ->
when (item) {
is FeedItem.Post -> PostView(item)
is FeedItem.Ad -> AdView(item)
is FeedItem.Suggested -> SuggestedView(item)
}
}
}
}
// Pengukuran performa melalui listState
@Composable
fun ScrollPerformance(listState: LazyListState) {
val scrollInfo = listState.layoutInfo
val visibleCount = scrollInfo.visibleItemsInfo.size
val total = scrollInfo.totalItemsCount
// derivedStateOf — tidak mengomposisi ulang jika nilai tidak berubah
val scrollProgress by remember {
derivedStateOf {
if (total > 0) visibleCount.toFloat() / total else 0f
}
}
LinearProgressIndicator(
progress = scrollProgress,
modifier = Modifier.fillMaxWidth().height(2.dp)
)
}
Kesalahan umum: (1) tidak ada key — elemen dikomposisi ulang pada setiap perubahan daftar; (2) menggunakan mutableStateListOf tanpa aliran snapshot — perubahan mungkin tidak dilacak; (3) memanggil fungsi suspend di dalam itemContent — menghentikan komposisi; (4) tanpa contentType untuk tipe campuran — Compose menggunakan kembali komposisi iklan untuk posting teks, menyebabkan artefak visual. Di IT Sectr kami menambahkan contentType ke semua daftar dengan tiga atau lebih jenis konten.
Pertanyaan yang Sering Diajukan
Periksa tiga parameter: (1) key — tanpa kunci stabil Compose tidak dapat menggunakan kembali komposisi, (2) contentType — untuk daftar dengan tipe elemen berbeda, (3) beyondBoundsPageCount — tingkatkan ke 2 untuk gambar. Pindahkan perhitungan berat di dalam itemContent ke remember dengan kunci berdasarkan data. Gunakan Modifier.drawWithContent alih-alih Image untuk grafis sederhana.
Gunakan LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) untuk jarak antar elemen. Untuk pemisah visual dengan garis, tambahkan item { Divider() } di antara items. Pemisah otomatis: items(items, key = { it.id }) { item -> ... }, dan di antara setiap elemen sisipkan item { HorizontalDivider() }. Untuk ini gunakan LazyListScope.items(list, key) { /* elemen */ } dan secara terpisah LazyListScope.item { Divider() }.
LazyColumn memiliki tinggi tak terbatas (fillMaxSize) secara default. Di dalam Column tanpa tinggi tetap, LazyColumn tidak dapat menentukan ukuran. Solusi: (1) beri LazyColumn tinggi tetap: Modifier.height(400.dp), (2) gunakan Modifier.weight(1f) di dalam Column, (3) jangan tempatkan LazyColumn dalam wadah yang dapat digulir vertikal — gunakan LazyColumn sebagai elemen root dengan item {} untuk header dan footer.
LazyColumn secara otomatis merespons perubahan State. Jika data disimpan dalam mutableStateListOf atau mutableStateOf, Compose hanya mengomposisi ulang elemen yang berubah. Untuk daftar dari ViewModel gunakan collectAsState() dengan Flow. Saat memperbarui daftar dengan key, Compose secara otomatis menganimasikan perubahan (penambahan, penghapusan, pemindahan) melalui Modifier.animateItemPlacement().
LazyColumn — untuk daftar satu kolom (feed, obrolan, komentar). LazyVerticalGrid — untuk grid (galeri, katalog, ikon). Jika elemen harus satu kolom di ponsel dan dua kolom di tablet — gunakan LazyVerticalGrid dengan GridCells.Adaptive. Jika Anda membutuhkan grid campuran (jumlah kolom berbeda dalam satu bagian) — gunakan LazyColumn dan di dalam items sisipkan LazyRow horizontal atau LazyVerticalGrid bersarang.
Kesimpulan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga