LazyColumn — apa itu, daftar efisien di Compose

Penulis: IT Sectr Diterbitkan: 2026-02-24 Waktu membaca: 10 mnt

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

  • LazyColumn — komponen Jetpack Compose untuk daftar vertikal dengan pemuatan malas, bagian dari Material Design 3.
  • LazyListState — status daftar: posisi gulir, elemen terlihat, gulir terprogram.
  • items API — fungsi items(), itemsIndexed(), items() dengan kunci untuk pengikatan data.
  • key — parameter untuk identifikasi stabil elemen, memungkinkan Compose menggunakan kembali komposisi.
  • LazyVerticalGrid — versi LazyColumn untuk grid dengan jumlah kolom tetap.

Apa itu LazyColumn di Jetpack Compose?

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.

Perbedaan dengan Column + ScrollState

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: mengikat data ke daftar

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.

kotlin
// 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: mengelola posisi gulir

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.

kotlin
// 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 vs RecyclerView: perbandingan pendekatan

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.

ParameterLazyColumn (Compose)RecyclerView (View)
Tata letakFungsi Composable (Kotlin DSL)File XML + ViewBinding
Adapteritems() / itemsIndexed() DSLRecyclerView.Adapter + ViewHolder
Pengurutan/penyaringanSnapshot state + derivedStateOfDiffUtil + AsyncListDiffer
AnimasiAnimatedVisibility + Modifier.animateItemDefaultItemAnimator
Tipe elemen berbedaitem {}/items() + when berdasarkan tipegetItemViewType + beberapa ViewHolder
Gulir ke posisilistState.animateScrollToItem()layoutManager.scrollToPosition()
PrefetchIn-process (buffer SubcomposeLayout)RecycledViewPool + GapWorker
Jaringan/paginasicollectAsLazyPagingItems() + Paging 3PagingDataAdapter + 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 dan LazyHorizontalGrid: grid

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).

kotlin
// 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: key, contentType, prefetch

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.

ParameterDeskripsiRekomendasi
keyPengidentifikasi elemen yang stabilSelalu tentukan key = { it.id }. Tanpa key, Compose menggunakan indeks, dan pengaturan ulang elemen merusak animasi.
contentTypeJenis konten untuk pemisahan kumpulanTentukan untuk daftar dengan tipe elemen berbeda (teks + gambar + iklan). Compose akan menggunakan kembali komposisi hanya dalam contentType yang sama.
beyondBoundsJumlah layar untuk prefetchTingkatkan ke 2–3 untuk daftar dengan gambar berat. Default 1 layar.
Modifier.animateItemAnimasi perubahan posisiGunakan untuk pengurutan dan penyaringan animasi (Compose 1.7+).
kotlin
// 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

LazyColumn menggulir lambat — apa yang harus dilakukan?

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.

Bagaimana cara menambahkan pemisah antara elemen LazyColumn?

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 di dalam Column tidak berfungsi — mengapa?

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.

Bagaimana cara memperbarui LazyColumn saat data berubah?

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 vs LazyVerticalGrid — mana yang harus dipilih?

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

  • LazyColumn — komponen Jetpack Compose untuk daftar vertikal malas dengan API deklaratif.
  • items() API menerima daftar dan blok DSL untuk setiap elemen; key wajib untuk performa.
  • LazyListState mengelola posisi gulir, menyediakan fungsi suspend untuk gulir terprogram.
  • contentType memisahkan kumpulan komposisi untuk daftar dengan tipe elemen berbeda.
  • LazyVerticalGrid / LazyRow — varian untuk grid dan daftar horizontal dengan items API yang sama.
  • LazyColumn memberikan 60 FPS untuk 10000+ elemen berkat SubcomposeLayout dan buffer prefetch.
  • Untuk proyek Compose baru gunakan LazyColumn; untuk proyek View-based — RecyclerView.

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.

Diskusikan proyek

Baca juga