LazyColumn — nedir, Compose'ta verimli listeler

Yazar: IT Sectr Yayınlanma: 2026-02-24 Okuma süresi: 10 dk

LazyColumn — temel yükleme ile kaydırılabilir listeleri görüntülemek için bir Jetpack Compose bileşenidir. RecyclerView'dan farklı olarak, LazyColumn ViewHolder'lar oluşturmaz ve XML kullanmaz — tüm öğeler composable fonksiyonlar aracılığıyla bildirimsel olarak tanımlanır. LazyColumn'un ne olduğunu, LazyListState ve items API'nin nasıl çalıştığını ve 60 FPS performansına nasıl ulaşılacağını gösteriyoruz. IT Sectr'de tüm yeni Compose projelerinde LazyColumn kullanıyoruz. Klasik yaklaşımla karşılaştırma için RecyclerView hakkındaki makaleyi okuyun.

Önemli Noktalar

  • LazyColumn — tembel yükleme ile dikey listeler için bir Jetpack Compose bileşeni, Material Design 3'ün parçası.
  • LazyListState — liste durumu: kaydırma konumu, görünür öğeler, programatik kaydırma.
  • items API — veri bağlama için anahtarlı items(), itemsIndexed(), items() fonksiyonları.
  • key — öğelerin kararlı tanımlanması için parametre, Compose'un kompozisyonu yeniden kullanmasını sağlar.
  • LazyVerticalGrid — sabit sütun sayılı ızgaralar için LazyColumn sürümü.

Jetpack Compose'ta LazyColumn Nedir?

LazyColumn, Foundation Compose kütüphanesinden, tembel yükleme ile öğelerin dikey listesini görüntüleyen bir composable fonksiyondur: yalnızca ekranda görünen veya yakındaki öğeler (önceden getirme bölgesi) oluşturulur ve birleştirilir. LazyColumn, LazyRow (yatay liste) ve LazyVerticalGrid (ızgara) ile birlikte Compose 1.0'da (2021) ortaya çıktı.

Tembel yükleme, SubcomposeLayout mekanizması aracılığıyla çalışır: LazyColumn kullanılabilir alanı ölçer, LayoutInfo'dan görünür aralığı ister ve yalnızca bu aralıktaki öğeleri birleştirir. Ekran dışına kaydırılan öğeler kompozisyondan ayrılır (önbellek arabelleği hariç). Google'a (Android Performance, 2026) göre LazyColumn, orta sınıf cihazlarda 10000+ öğeli bir listede kaydırma yaparken 60 FPS'yi korur.

Column + ScrollState'den Farkı

Column dikey kaydırma ile — tüm öğelerin verticalScroll değiştiricisi uygulanmış şekilde alt alta normal düzenidir. Column tembel değildir: görünür olmasalar bile tüm alt öğeler hemen birleştirilir. 200+ öğeli bir liste için Column, başlangıçta önemli gecikmeye neden olur. LazyColumn yalnızca görünür öğeler + arabelleği (varsayılan olarak 1 ekran ileri/geri) birleştirir, bu da onu uzun listeler için vazgeçilmez kılar.

items API: listeye veri bağlama

items API, LazyColumn için bir dizi uzantı fonksiyonudur ve her öğe için bir veri listesi ve DSL tanımlayıcısı kabul eder. Ana fonksiyonlar şunlardır: sabit sayı için items(count, key, itemContent), liste için items(list, key, itemContent) ve dizinle itemsIndexed(list, key, itemContent). key parametresi performans için zorunludur — Compose'a kararlı bir öğe tanımlayıcısı sağlar.

kotlin
// itemContent ile Temel LazyColumn
@Composable
fun ArticleList(articles: List<Article>) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        // Header — tembel değil, her zaman başta
        item {
            Text("Son makaleler", style = MaterialTheme.typography.headlineMedium)
        }

        // Kimliğe göre anahtarlı makale listesi
        items(articles, key = { it.id }) { article ->
            ArticleCard(
                title = article.title,
                summary = article.summary,
                onClick = { onArticleClick(article.id) }
            )
        }

        // Yükleme göstergeli Footer
        item {
            CircularProgressIndicator(
                modifier = Modifier.fillMaxWidth().padding(16.dp)
            )
        }
    }
}

// Özel liste öğesi
@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(): listede her zaman bulunan tek öğeler (başlık, altbilgi, ayırıcılar) için item {} kullanın. Dinamik veri listesi için items() kullanın. items() bir Iterable veya Int ve her öğe için çağrılan bir DSL bloğu kabul eder. Bir LazyColumn'da item ve items'ı birleştirin: çağrıların sırası görüntüleme sırasını belirler.

LazyListState: kaydırma konumu yönetimi

LazyListState, liste durumunu depolayan bir nesnedir: geçerli kaydırma konumu (firstVisibleItemIndex, firstVisibleItemScrollOffset), görünür öğeler (layoutInfo) ve programatik kaydırma yöntemleri (scrollToItem, animateScrollToItem). LazyListState, rememberLazyListState() aracılığıyla oluşturulur ve state parametresi aracılığıyla LazyColumn'a iletilir.

kotlin
// Kaydırma konumu korumalı LazyColumn
@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)
            }
        }

        // "Yukarı" düğmesi 10. öğeden sonra görünür
        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, "Başa dön")
            }
        }
    }
}

// Görünür öğe bilgilerini alma
@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("$totalItems öğeden $firstItem–$lastItem gösteriliyor")
}

Programatik kaydırma, listState'in askıya alma fonksiyonları aracılığıyla gerçekleştirilir: scrollToItem(index, scrollOffset) — anlık hareket (animasyonsuz), animateScrollToItem(index) — animasyonlu. Bir coroutine'den kaydırma için LaunchedEffect veya coroutineScope.launch kullanın. Önemli: kompozisyondan (@Composable içinden) askıya alma fonksiyonlarını çağırmayın — lambda (onClick, LaunchedEffect) kullanın.

LazyColumn vs RecyclerView: yaklaşım karşılaştırması

LazyColumn ve RecyclerView aynı sorunu çözer — uzun listelerin verimli görüntülenmesi. Fark mimaridedir: RecyclerView, ViewHolder ve Adapter (klasik Android Görünümü) kullanır, LazyColumn ise XML veya ViewHolder olmadan bildirimsel composable fonksiyonlar kullanır. Seçim, projenin teknoloji yığınına bağlıdır: Compose vs View tabanlı UI.

ParametreLazyColumn (Compose)RecyclerView (View)
DüzenComposable fonksiyonlar (Kotlin DSL)XML dosyaları + ViewBinding
Adaptöritems() / itemsIndexed() DSLRecyclerView.Adapter + ViewHolder
Sıralama/FiltrelemeSnapshot state + derivedStateOfDiffUtil + AsyncListDiffer
AnimasyonAnimatedVisibility + Modifier.animateItemDefaultItemAnimator
Farklı öğe türleriitem {}/items() + türe göre whengetItemViewType + birden çok ViewHolder
Konuma kaydırmalistState.animateScrollToItem()layoutManager.scrollToPosition()
Önceden getirmeSüreç içi (SubcomposeLayout arabelleği)RecycledViewPool + GapWorker
Ağ/SayfalamacollectAsLazyPagingItems() + Paging 3PagingDataAdapter + Paging 3

Google'ın önerisi (Android Developers, 2026): Jetpack Compose'taki yeni projeler için LazyColumn kullanın. View tabanlı projeler için RecyclerView kullanın. LazyColumn, APK'ya yaklaşık 3–5 MB ekleyen Compose'u gerektirir — eski cihazları desteklerken bunu göz önünde bulundurun. Bir proje zaten View sistemi kullanıyorsa, LazyColumn AndroidView olmadan aynı ekranda XML düzeniyle bir arada bulunamaz — bu gibi durumlarda RecyclerView'da kalmak daha kolaydır.

LazyVerticalGrid ve LazyHorizontalGrid: ızgaralar

LazyVerticalGrid, sabit sütun sayılı ızgaralar için LazyColumn sürümüdür. LazyHorizontalGrid, sabit satırlı yatay bir ızgaradır. Her iki bileşen de LazyColumn ile aynı tembel yükleme ilkelerini ve aynı items API'sini kullanır. columns parametresi, GridCells.Fixed(N) veya GridCells.Adaptive(minSize) aracılığıyla sütun sayısını belirler.

kotlin
// Uyarlanabilir sütunlu ızgara
@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)
            )
        }
    }
}

// Yatay liste (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, minimum hücre boyutuna göre otomatik olarak sütun sayısını hesaplar. Örneğin, GridCells.Adaptive(128.dp) 360 dp genişliğindeki bir ekranda 2 sütun, 600 dp'lik bir tablette — 4 sütun yerleştirir. GridCells.Fixed(2) her zaman tam olarak 2 sütun gösterir. Adaptive, çoklu ekran uyarlaması için tercih edilir — medya sorguları olmadan otomatik olarak ekran genişliğine uyum sağlar.

LazyColumn Performansı: key, contentType, prefetch

LazyColumn performansı üç faktöre bağlıdır: anahtar kararlılığı (key), içerik türü (contentType) ve önceden getirme arabelleği (beyondBounds). Varsayılan olarak, LazyColumn 1 ekran ileri ve 1 geri arabelleğe alır. Google (Android Developers, 2026), farklı öğe türlerine veya karmaşık kompozisyona sahip listeler için bu parametrelerin yapılandırılmasını önerir.

ParametreAçıklamaÖneri
keyKararlı öğe tanımlayıcısıHer zaman key = { it.id } belirtin. Anahtar olmadan Compose dizini kullanır ve öğelerin yeniden sıralanması animasyonu bozar.
contentTypeHavuz ayırması için içerik türüFarklı öğe türlerine (metin + resim + reklam) sahip listeler için belirtin. Compose, kompozisyonu yalnızca aynı contentType içinde yeniden kullanır.
beyondBoundsÖnceden getirme ekranı sayısıAğır resimler içeren listeler için 2–3'e yükseltin. Varsayılan 1 ekrandır.
Modifier.animateItemKonum değişikliği animasyonuAnimasyonlu sıralama ve filtreleme için kullanın (Compose 1.7+).
kotlin
// contentType ve prefetch ile optimize edilmiş LazyColumn
@Composable
fun OptimizedFeed(items: List<FeedItem>) {
    val listState = rememberLazyListState()

    LazyColumn(
        state = listState,
        modifier = Modifier.fillMaxSize(),
        beyondBoundsPageCount = 2  // 2 ekran önceden getirme
    ) {
        items(
            items = items,
            key = { it.id },
            contentType = { it.type }  // türe göre bölme
        ) { item ->
            when (item) {
                is FeedItem.Post -> PostView(item)
                is FeedItem.Ad -> AdView(item)
                is FeedItem.Suggested -> SuggestedView(item)
            }
        }
    }
}

// listState ile performans ölçümü
@Composable
fun ScrollPerformance(listState: LazyListState) {
    val scrollInfo = listState.layoutInfo
    val visibleCount = scrollInfo.visibleItemsInfo.size
    val total = scrollInfo.totalItemsCount

    // derivedStateOf — değer değişmediyse yeniden birleştirmez
    val scrollProgress by remember {
        derivedStateOf {
            if (total > 0) visibleCount.toFloat() / total else 0f
        }
    }

    LinearProgressIndicator(
        progress = scrollProgress,
        modifier = Modifier.fillMaxWidth().height(2.dp)
    )
}

Yaygın hatalar: (1) anahtar eksikliği — öğeler her liste değişikliğinde yeniden birleştirilir; (2) anlık görüntü akışları olmadan mutableStateListOf kullanımı — değişiklikler izlenmeyebilir; (3) itemContent içinde askıya alma fonksiyonlarını çağırmak — kompozisyonu kesintiye uğratır; (4) karışık türler için contentType yok — Compose metin gönderileri için reklam kompozisyonunu yeniden kullanarak görsel yapaylıklara neden olur. IT Sectr'de üç veya daha fazla içerik türüne sahip tüm listelere contentType ekliyoruz.

Sıkça Sorulan Sorular

LazyColumn yavaş kaydırılıyor — ne yapmalı?

Üç parametreyi kontrol edin: (1) key — kararlı bir anahtar olmadan Compose kompozisyonu yeniden kullanamaz, (2) contentType — farklı öğe türlerine sahip listeler için, (3) beyondBoundsPageCount — resimler için 2'ye yükseltin. itemContent içindeki ağır hesaplamaları, verilere göre anahtarla remember'a taşıyın. Basit grafikler için Image yerine Modifier.drawWithContent kullanın.

LazyColumn öğeleri arasına ayırıcı nasıl eklenir?

Öğeler arasında boşluk için LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) kullanın. Çizgili görsel ayırıcı için items arasına item { Divider() } ekleyin. Otomatik ayırıcı: items(items, key = { it.id }) { item -> ... } ve her öğe arasına item { HorizontalDivider() } ekleyin. Bunun için LazyListScope.items(list, key) { /* öğe */ } ve ayrı olarak LazyListScope.item { Divider() } kullanın.

Column içindeki LazyColumn çalışmıyor — neden?

LazyColumn varsayılan olarak sonsuz yüksekliğe (fillMaxSize) sahiptir. Sabit yüksekliği olmayan bir Column içinde LazyColumn boyutunu belirleyemez. Çözüm: (1) LazyColumn'a sabit bir yükseklik ayarlayın: Modifier.height(400.dp), (2) Column içinde Modifier.weight(1f) kullanın, (3) LazyColumn'u dikey olarak kaydırılabilir bir kapsayıcı içinde iç içe koymayın — başlık ve altbilgi için item {} ile LazyColumn'u kök öğe olarak kullanın.

Veriler değiştiğinde LazyColumn nasıl güncellenir?

LazyColumn otomatik olarak State değişikliklerine tepki verir. Veriler mutableStateListOf veya mutableStateOf içinde depolanıyorsa, Compose yalnızca değişen öğeleri yeniden birleştirir. ViewModel'den gelen listeler için Flow ile collectAsState() kullanın. Liste anahtarla güncellendiğinde, Compose Modifier.animateItemPlacement() aracılığıyla değişiklikleri (ekleme, çıkarma, yeniden sıralama) otomatik olarak canlandırır.

LazyColumn vs LazyVerticalGrid — hangisi seçilmeli?

LazyColumn — tek sütunlu listeler için (akış, sohbet, yorumlar). LazyVerticalGrid — ızgaralar için (galeri, katalog, simgeler). Öğeler telefonda bir sütunda, tablette iki sütunda olmalıysa — GridCells.Adaptive ile LazyVerticalGrid kullanın. Karma bir ızgara gerekiyorsa (bir bölümde farklı sütun sayıları) — LazyColumn kullanın ve öğelerin içine yatay LazyRow veya iç içe LazyVerticalGrid ekleyin.

Özet

  • LazyColumn — bildirimsel API ile tembel dikey listeler için bir Jetpack Compose bileşeni.
  • items() API bir liste ve her öğe için bir DSL bloğu kabul eder; performans için key zorunludur.
  • LazyListState kaydırma konumunu yönetir, programatik kaydırma için askıya alma fonksiyonları sağlar.
  • contentType farklı öğe türlerine sahip listeler için kompozisyon havuzlarını ayırır.
  • LazyVerticalGrid / LazyRow — aynı items API'sine sahip ızgaralar ve yatay listeler için varyantlar.
  • LazyColumn, SubcomposeLayout ve önceden getirme arabelleği sayesinde 10000+ öğe için 60 FPS korur.
  • Yeni Compose projeleri için LazyColumn kullanın; View tabanlı projeler için — RecyclerView.

Anahtar teslim bir mobil uygulama geliştireceğiz

IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.

Projeyi tartış

Ayrıca okuyun