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
items(), itemsIndexed(), items() fonksiyonları.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 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, 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.
// 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, 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.
// 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 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.
| Parametre | LazyColumn (Compose) | RecyclerView (View) |
|---|---|---|
| Düzen | Composable fonksiyonlar (Kotlin DSL) | XML dosyaları + ViewBinding |
| Adaptör | items() / itemsIndexed() DSL | RecyclerView.Adapter + ViewHolder |
| Sıralama/Filtreleme | Snapshot state + derivedStateOf | DiffUtil + AsyncListDiffer |
| Animasyon | AnimatedVisibility + Modifier.animateItem | DefaultItemAnimator |
| Farklı öğe türleri | item {}/items() + türe göre when | getItemViewType + birden çok ViewHolder |
| Konuma kaydırma | listState.animateScrollToItem() | layoutManager.scrollToPosition() |
| Önceden getirme | Süreç içi (SubcomposeLayout arabelleği) | RecycledViewPool + GapWorker |
| Ağ/Sayfalama | collectAsLazyPagingItems() + Paging 3 | PagingDataAdapter + 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, 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.
// 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ı üç 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.
| Parametre | Açıklama | Öneri |
|---|---|---|
| key | Kararlı öğe tanımlayıcısı | Her zaman key = { it.id } belirtin. Anahtar olmadan Compose dizini kullanır ve öğelerin yeniden sıralanması animasyonu bozar. |
| contentType | Havuz 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.animateItem | Konum değişikliği animasyonu | Animasyonlu sıralama ve filtreleme için kullanın (Compose 1.7+). |
// 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
Üç 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.
Öğ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.
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.
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 — 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
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.
Ayrıca okuyun