LazyColumn — Jetpack Compose komponens görgethető listák lusta betöltéssel történő megjelenítéséhez. A RecyclerView-val ellentétben a LazyColumn nem hoz létre ViewHolder-okat és nem használ XML-t — minden elem deklaratívan, composable függvényeken keresztül kerül leírásra. Megmutatjuk, mi az a LazyColumn, hogyan működik a LazyListState és items API, és hogyan érhető el a 60 FPS teljesítmény. Az IT Sectr-nél a LazyColumn-t használjuk minden új Compose projektben. A klasszikus megközelítéssel való összehasonlításhoz olvassa el a RecyclerView-ról szóló cikket.
Főbb pontok
items(), itemsIndexed(), items() függvények kulcsokkal az adatok kötéséhez.LazyColumn — egy composable függvény a Foundation Compose könyvtárból, amely egy függőleges elemekből álló listát jelenít meg lusta (lazy) betöltéssel: csak azok az elemek jönnek létre és komponálódnak, amelyek láthatók a képernyőn vagy annak közelében vannak (prefetch zóna). A LazyColumn a Compose 1.0-ban (2021) jelent meg a LazyRow (vízszintes lista) és a LazyVerticalGrid (rács) mellett.
Lusta betöltés a SubcomposeLayout mechanizmuson keresztül működik: a LazyColumn megméri a rendelkezésre álló helyet, lekéri a látható tartományt a LayoutInfo-tól, és csak az ebben a tartományban lévő elemeket komponálja. A képernyőről eltűnt elemek elhagyják a kompozíciót (kivéve a prefetch puffert). A Google adatai szerint (Android Performance, 2026) a LazyColumn 60 FPS-t biztosít egy 10000+ elemből álló lista görgetésekor középkategóriás eszközökön.
Column függőleges görgetéssel — az összes elem egymás alá rendezése a verticalScroll módosító alkalmazásával. A Column nem lusta: az összes gyermekelem azonnal komponálódik, még akkor is, ha nem láthatók. Egy 200+ elemű lista esetén a Column jelentős késleltetést okoz induláskor. A LazyColumn csak a látható elemeket + puffert (alapértelmezés szerint 1 képernyő előre/hátra) komponálja, ami nélkülözhetetlenné teszi hosszú listák esetén.
items API — kiterjesztő függvények halmaza a LazyColumn-hoz, amelyek egy adatlistát és egy DSL leírót fogadnak el minden elemhez. Fő függvények: items(count, key, itemContent) fix számhoz, items(list, key, itemContent) listához, itemsIndexed(list, key, itemContent) indexszel. A key paraméter — kötelező feltétel a teljesítményhez — stabil azonosítót ad a Compose-nak az elemhez.
// Alap LazyColumn itemContent-tel
@Composable
fun ArticleList(articles: List<Article>) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// Header — nem lusta, mindig az elején
item {
Text("Legutóbbi cikkek", style = MaterialTheme.typography.headlineMedium)
}
// Cikkek listája id kulccsal
items(articles, key = { it.id }) { article ->
ArticleCard(
title = article.title,
summary = article.summary,
onClick = { onArticleClick(article.id) }
)
}
// Footer betöltési jelzővel
item {
CircularProgressIndicator(
modifier = Modifier.fillMaxWidth().padding(16.dp)
)
}
}
}
// Egyéni listaelem
@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(): használja a item {}-t egyedi elemekhez, amelyek mindig jelen vannak a listában (header, footer, elválasztók). Használja a items()-t dinamikus adatlistához. Az items() Iterable vagy Int típust és egy DSL blokkot fogad el, amely minden elemhez meghívásra kerül. Kombinálja az item és items elemeket egy LazyColumn-ban: a hívások sorrendje határozza meg a megjelenítés sorrendjét.
LazyListState — objektum, amely tárolja a lista állapotát: aktuális görgetési pozíció (firstVisibleItemIndex, firstVisibleItemScrollOffset), látható elemek (layoutInfo) és programozott görgetési módszerek (scrollToItem, animateScrollToItem). A LazyListState a rememberLazyListState() segítségével jön létre, és a state paraméteren keresztül kerül átadásra a LazyColumn-nak.
// LazyColumn görgetési pozíció megtartásával
@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)
}
}
// A "Fel" gomb a 10. elem után jelenik meg
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, "Felülre")
}
}
}
}
// Információk lekérése a látható elemekről
@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("$firstItem–$lastItem megjelenítve $totalItems-ból")
}
Programozott görgetés a listState suspend függvényein keresztül történik: scrollToItem(index, scrollOffset) — azonnali mozgatás (animáció nélkül), animateScrollToItem(index) — animációval. Korutinból történő görgetéshez használja a LaunchedEffect vagy coroutineScope.launch függvényeket. Fontos: ne hívjon suspend függvényeket kompozícióból (@Composable-en belül) — használjon lambdákat (onClick, LaunchedEffect).
LazyColumn és RecyclerView ugyanazt a feladatot oldja meg — hosszú listák hatékony megjelenítését. A különbség az architektúrában van: a RecyclerView ViewHolder-t és Adapter-t használ (klasszikus Android View), a LazyColumn — deklaratív composable függvényeket XML és ViewHolder nélkül. A választás a projekt technológiai stackjétől függ: Compose vs View-based UI.
| Paraméter | LazyColumn (Compose) | RecyclerView (View) |
|---|---|---|
| Elrendezés | Composable függvények (Kotlin DSL) | XML fájlok + ViewBinding |
| Adapter | items() / itemsIndexed() DSL | RecyclerView.Adapter + ViewHolder |
| Rendezés/szűrés | Snapshot state + derivedStateOf | DiffUtil + AsyncListDiffer |
| Animáció | AnimatedVisibility + Modifier.animateItem | DefaultItemAnimator |
| Különböző elem típusok | item {}/items() + when típus szerint | getItemViewType + több ViewHolder |
| Görgetés pozícióra | listState.animateScrollToItem() | layoutManager.scrollToPosition() |
| Prefetch | In-process (SubcomposeLayout puffer) | RecycledViewPool + GapWorker |
| Hálózat/lapozás | collectAsLazyPagingItems() + Paging 3 | PagingDataAdapter + Paging 3 |
Google ajánlás (Android Developers, 2026): új Jetpack Compose projektekhez használja a LazyColumn-t. View-alapú projektekhez használja a RecyclerView-t. A LazyColumn Compose-t igényel, ami körülbelül 3–5 MB-ot ad az APK-hoz — ezt vegye figyelembe a régi eszközök támogatásánál. Ha a projekt már View rendszert használ, a LazyColumn nem létezhet együtt ugyanazon a képernyőn XML elrendezéssel AndroidView nélkül — ilyen esetekben egyszerűbb a RecyclerView-nál maradni.
LazyVerticalGrid — a LazyColumn verziója fix oszlopszámú (columns) rácsokhoz. LazyHorizontalGrid — vízszintes rács fix sorokkal (rows). Mindkét komponens ugyanazokat a lusta betöltési elveket és ugyanazt az items API-t használja, mint a LazyColumn. A columns paraméter GridCells.Fixed(N) vagy GridCells.Adaptive(minSize) segítségével határozza meg az oszlopok számát.
// Rács adaptív oszlopokkal
@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)
)
}
}
}
// Vízszintes lista (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 automatikusan kiszámítja az oszlopok számát a cella minimális mérete alapján. Például a GridCells.Adaptive(128.dp) egy 360 dp széles képernyőn 2 oszlopot, egy 600 dp-s táblagépen 4 oszlopot helyez el. A GridCells.Fixed(2) mindig pontosan 2 oszlopot mutat. Az Adaptive előnyösebb a többképernyős adaptációhoz — automatikusan alkalmazkodik a képernyő szélességéhez médialekérdezések nélkül.
Teljesítmény A LazyColumn három tényezőtől függ: a kulcsok stabilitásától (key), a tartalom típusától (contentType) és a prefetch puffer méretétől (beyondBounds). Alapértelmezés szerint a LazyColumn 1 képernyőt pufferel előre és 1-et hátra. A Google (Android Developers, 2026) javasolja ezen paraméterek konfigurálását különböző elem típusú vagy összetett kompozíciójú listákhoz.
| Paraméter | Leírás | Ajánlás |
|---|---|---|
| key | Az elem stabil azonosítója | Mindig adja meg a key = { it.id } paramétert. Key nélkül a Compose indexet használ, és az elemek átrendezése elrontja az animációt. |
| contentType | Tartalom típusa a poolok szétválasztásához | Adja meg különböző elem típusú listákhoz (szöveg + kép + hirdetés). A Compose csak ugyanazon contentType-on belül használja újra a kompozíciót. |
| beyondBounds | Képernyők száma a prefetch-hez | Növelje 2–3-ra nehéz képeket tartalmazó listák esetén. Alapértelmezés: 1 képernyő. |
| Modifier.animateItem | Pozícióváltozás animációja | Animált rendezéshez és szűréshez használja (Compose 1.7+). |
// Optimalizált LazyColumn contentType és prefetch segítségével
@Composable
fun OptimizedFeed(items: List<FeedItem>) {
val listState = rememberLazyListState()
LazyColumn(
state = listState,
modifier = Modifier.fillMaxSize(),
beyondBoundsPageCount = 2 // prefetch 2 képernyő
) {
items(
items = items,
key = { it.id },
contentType = { it.type } // szétválasztás típusok szerint
) { item ->
when (item) {
is FeedItem.Post -> PostView(item)
is FeedItem.Ad -> AdView(item)
is FeedItem.Suggested -> SuggestedView(item)
}
}
}
}
// Teljesítménymérés listState-en keresztül
@Composable
fun ScrollPerformance(listState: LazyListState) {
val scrollInfo = listState.layoutInfo
val visibleCount = scrollInfo.visibleItemsInfo.size
val total = scrollInfo.totalItemsCount
// derivedStateOf — nem komponálódik újra, ha az érték nem változott
val scrollProgress by remember {
derivedStateOf {
if (total > 0) visibleCount.toFloat() / total else 0f
}
}
LinearProgressIndicator(
progress = scrollProgress,
modifier = Modifier.fillMaxWidth().height(2.dp)
)
}
Gyakori hibák: (1) key hiánya — az elemek újrakomponálódnak a lista minden változásakor; (2) mutableStateListOf használata snapshot folyamok nélkül — a változások nem feltétlenül követhetők nyomon; (3) suspend függvények hívása itemContent-on belül — megszakítja a kompozíciót; (4) contentType hiánya vegyes típusokhoz — a Compose újrahasználja a hirdetés kompozícióját egy szöveges bejegyzéshez, vizuális hibákat okozva. Az IT Sectr-nél contentType-t adunk minden olyan listához, amely három vagy több tartalomtípust tartalmaz.
Gyakran Ismételt Kérdések
Ellenőrizzen három paramétert: (1) key — stabil kulcs nélkül a Compose nem tudja újrahasználni a kompozíciót, (2) contentType — különböző elem típusú listákhoz, (3) beyondBoundsPageCount — növelje 2-re képek esetén. A nehéz számításokat itemContent-on belül helyezze át remember-be adatkulccsal. Egyszerű grafikához Image helyett használja a Modifier.drawWithContent függvényt.
Használja a LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) paramétert az elemek közötti távközökhöz. Vizuális elválasztóhoz vonallal adjon hozzá item { Divider() } elemeket az items között. Automatikus elválasztó: items(items, key = { it.id }) { item -> ... }, és minden elem közé illesszen be item { HorizontalDivider() } elemet. Ehhez használja a LazyListScope.items(list, key) { /* elem */ } és külön a LazyListScope.item { Divider() } függvényeket.
LazyColumn alapértelmezés szerint végtelen magasságú (fillMaxSize). Fix magasság nélküli Column-on belül a LazyColumn nem tudja meghatározni a méretet. Megoldás: (1) adjon a LazyColumn-nak fix magasságot: Modifier.height(400.dp), (2) használja a Modifier.weight(1f) paramétert Column-on belül, (3) ne helyezze a LazyColumn-t függőlegesen görgethető konténerbe — használja a LazyColumn-t gyökérelemként item {} segítségével header és footer számára.
LazyColumn automatikusan reagál a State változásaira. Ha az adatok mutableStateListOf vagy mutableStateOf típusban vannak tárolva, a Compose csak a megváltozott elemeket komponálja újra. ViewModel-ből származó listákhoz használja a collectAsState() függvényt Flow-val. A lista key-vel történő frissítésekor a Compose automatikusan animálja a változásokat (hozzáadás, eltávolítás, áthelyezés) a Modifier.animateItemPlacement() segítségével.
LazyColumn — egy oszlopos listákhoz (feed, chat, hozzászólások). LazyVerticalGrid — rácsokhoz (galéria, katalógus, ikonok). Ha az elemeknek a telefonon egy oszlopban, a táblagépen pedig két oszlopban kell lenniük — használja a LazyVerticalGrid-et GridCells.Adaptive beállítással. Ha vegyes rácsra van szükség (különböző oszlopszám egy szekcióban) — használja a LazyColumn-t, és az items-en belül illesszen be vízszintes LazyRow vagy egymásba ágyazott LazyVerticalGrid elemeket.
Összegzés
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is