LazyColumn — co to je, efektivní seznamy v Compose

Autor: IT Sectr Publikováno: 2026-02-24 Doba čtení: 10 min

LazyColumn — komponenta Jetpack Compose pro zobrazení posouvatelných seznamů s líným načítáním. Na rozdíl od RecyclerView, LazyColumn nevytváří ViewHoldery a nepoužívá XML — všechny prvky jsou popsány deklarativně pomocí composable funkcí. Ukazujeme, co je LazyColumn, jak funguje LazyListState a items API, a jak dosáhnout výkonu na úrovni 60 FPS. V IT Sectr používáme LazyColumn ve všech nových Compose projektech. Pro srovnání s klasickým přístupem si přečtěte článek o RecyclerView.

Hlavní body

  • LazyColumn — komponenta Jetpack Compose pro vertikální seznamy s líným načítáním, součást Material Design 3.
  • LazyListState — stav seznamu: pozice posouvání, viditelné prvky, programové posouvání.
  • items API — funkce items(), itemsIndexed(), items() s klíči pro vázání dat.
  • key — parametr pro stabilní identifikaci prvků, umožňující Compose znovu použít kompozici.
  • LazyVerticalGrid — verze LazyColumn pro mřížky s pevným počtem sloupců.

Co je LazyColumn v Jetpack Compose?

LazyColumn — je composable funkce z knihovny Foundation Compose, která zobrazuje vertikální seznam prvků s líným (lazy) načítáním: vytvářejí se a komponují pouze ty prvky, které jsou viditelné na obrazovce nebo se nacházejí v blízkosti (zóna prefetch). LazyColumn se objevil v Compose 1.0 (2021) spolu s LazyRow (horizontální seznam) a LazyVerticalGrid (mřížka).

Líné načítání funguje prostřednictvím mechanismu SubcomposeLayout: LazyColumn změří dostupný prostor, vyžádá viditelný rozsah z LayoutInfo a komponuje pouze prvky v tomto rozsahu. Prvky, které opustily obrazovku, opouštějí kompozici (s výjimkou vyrovnávací paměti prefetch). Podle údajů Google (Android Performance, 2026) poskytuje LazyColumn 60 FPS při posouvání seznamu 10000+ prvků na zařízeních střední třídy.

Rozdíl oproti Column + ScrollState

Column s vertikálním posouváním — je běžné uspořádání všech prvků pod sebou s aplikací modifikátoru verticalScroll. Column není líný: všechny podřízené prvky jsou komponovány okamžitě, i když nejsou viditelné. Pro seznam 200+ prvků způsobuje Column výrazné zpoždění při spuštění. LazyColumn komponuje pouze viditelné prvky + vyrovnávací paměť (výchozí 1 obrazovka dopředu/dozadu), což ho činí nepostradatelným pro dlouhé seznamy.

items API: vázání dat k seznamu

items API — sada rozšiřujících funkcí pro LazyColumn, které přijímají seznam dat a DSL popisovač pro každý prvek. Hlavní funkce: items(count, key, itemContent) pro pevný počet, items(list, key, itemContent) pro seznam, itemsIndexed(list, key, itemContent) s indexem. Parametr key — povinná podmínka pro výkon — dává Compose stabilní identifikátor prvku.

kotlin
// Základní LazyColumn s itemContent
@Composable
fun ArticleList(articles: List<Article>) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        // Header — není líný, vždy na začátku
        item {
            Text("Nejnovější články", style = MaterialTheme.typography.headlineMedium)
        }

        // Seznam článků s klíčem podle id
        items(articles, key = { it.id }) { article ->
            ArticleCard(
                title = article.title,
                summary = article.summary,
                onClick = { onArticleClick(article.id) }
            )
        }

        // Footer s indikátorem načítání
        item {
            CircularProgressIndicator(
                modifier = Modifier.fillMaxWidth().padding(16.dp)
            )
        }
    }
}

// Vlastní prvek seznamu
@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(): použijte item {} pro jednotlivé prvky, které jsou v seznamu vždy přítomny (header, footer, oddělovače). Použijte items() pro dynamický seznam dat. items() přijímá Iterable nebo Int a DSL blok, který je volán pro každý prvek. Kombinujte item a items v jednom LazyColumn: pořadí volání určuje pořadí zobrazení.

LazyListState: správa pozice posouvání

LazyListState — objekt ukládající stav seznamu: aktuální pozici posouvání (firstVisibleItemIndex, firstVisibleItemScrollOffset), viditelné prvky (layoutInfo) a metody programového posouvání (scrollToItem, animateScrollToItem). LazyListState se vytváří pomocí rememberLazyListState() a předává se LazyColumn prostřednictvím parametru state.

kotlin
// LazyColumn s uchováním pozice posouvání
@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)
            }
        }

        // Tlačítko "Nahoru" se objeví po 10. prvku
        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, "Na začátek")
            }
        }
    }
}

// Získání informací o viditelných prvcích
@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("Zobrazeno $firstItem–$lastItem z $totalItems")
}

Programové posouvání se provádí pomocí suspend funkcí listState: scrollToItem(index, scrollOffset) — okamžitý přesun (bez animace), animateScrollToItem(index) — s animací. Pro posouvání z korutiny použijte LaunchedEffect nebo coroutineScope.launch. Důležité: nevolejte suspend funkce z kompozice (uvnitř @Composable) — použijte lambdy (onClick, LaunchedEffect).

LazyColumn vs RecyclerView: srovnání přístupů

LazyColumn a RecyclerView řeší stejný úkol — efektivní zobrazení dlouhých seznamů. Rozdíl je v architektuře: RecyclerView používá ViewHolder a Adapter (klasické Android View), LazyColumn — deklarativní composable funkce bez XML a ViewHolder. Výběr závisí na technologickém stacku projektu: Compose vs View-based UI.

ParametrLazyColumn (Compose)RecyclerView (View)
RozvrženíComposable funkce (Kotlin DSL)XML soubory + ViewBinding
Adaptéritems() / itemsIndexed() DSLRecyclerView.Adapter + ViewHolder
Řazení/filtrováníSnapshot state + derivedStateOfDiffUtil + AsyncListDiffer
AnimaceAnimatedVisibility + Modifier.animateItemDefaultItemAnimator
Různé typy prvkůitem {}/items() + when podle typugetItemViewType + více ViewHolderů
Posouvání na pozicilistState.animateScrollToItem()layoutManager.scrollToPosition()
PrefetchIn-process (SubcomposeLayout buffer)RecycledViewPool + GapWorker
Síť/stránkovánícollectAsLazyPagingItems() + Paging 3PagingDataAdapter + Paging 3

Doporučení Google (Android Developers, 2026): pro nové projekty na Jetpack Compose používejte LazyColumn. Pro View-based projekty používejte RecyclerView. LazyColumn vyžaduje Compose, který přidává do APK přibližně 3–5 MB — zohledněte to při podpoře starých zařízení. Pokud projekt již používá systém View, LazyColumn nemůže koexistovat na stejné obrazovce s XML rozvržením bez AndroidView — v takových případech je jednodušší zůstat u RecyclerView.

LazyVerticalGrid a LazyHorizontalGrid: mřížky

LazyVerticalGrid — verze LazyColumn pro mřížky s pevným počtem sloupců (columns). LazyHorizontalGrid — horizontální mřížka s pevným počtem řádků (rows). Obě komponenty používají stejné principy líného načítání a stejné items API jako LazyColumn. Parametr columns určuje počet sloupců pomocí GridCells.Fixed(N) nebo GridCells.Adaptive(minSize).

kotlin
// Mřížka s adaptivními sloupci
@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)
            )
        }
    }
}

// Horizontální seznam (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 automaticky vypočítá počet sloupců na základě minimální velikosti buňky. Například GridCells.Adaptive(128.dp) na obrazovce šířky 360 dp umístí 2 sloupce, na tabletu 600 dp — 4 sloupce. GridCells.Fixed(2) vždy zobrazí přesně 2 sloupce. Adaptive je preferován pro multi-obrazovkovou adaptaci — automaticky se přizpůsobuje šířce obrazovky bez media dotazů.

Výkon LazyColumn: key, contentType, prefetch

Výkon LazyColumn závisí na třech faktorech: stabilitě klíčů (key), typu obsahu (contentType) a vyrovnávací paměti prefetch (beyondBounds). Ve výchozím nastavení LazyColumn bufferuje 1 obrazovku dopředu a 1 dozadu. Google (Android Developers, 2026) doporučuje konfigurovat tyto parametry pro seznamy s různými typy prvků nebo složitou kompozicí.

ParametrPopisDoporučení
keyStabilní identifikátor prvkuVždy uveďte key = { it.id }. Bez key Compose používá index a změna pořadí prvků kazí animaci.
contentTypeTyp obsahu pro oddělení poolůUveďte pro seznamy s různými typy prvků (text + obrázek + reklama). Compose bude znovu používat kompozici pouze v rámci stejného contentType.
beyondBoundsPočet obrazovek pro prefetchZvyšte na 2–3 pro seznamy s těžkými obrázky. Výchozí 1 obrazovka.
Modifier.animateItemAnimace změny pozicePoužijte pro animované řazení a filtrování (Compose 1.7+).
kotlin
// Optimalizovaný LazyColumn s contentType a prefetch
@Composable
fun OptimizedFeed(items: List<FeedItem>) {
    val listState = rememberLazyListState()

    LazyColumn(
        state = listState,
        modifier = Modifier.fillMaxSize(),
        beyondBoundsPageCount = 2  // prefetch 2 obrazovky
    ) {
        items(
            items = items,
            key = { it.id },
            contentType = { it.type }  // rozdělení podle typů
        ) { item ->
            when (item) {
                is FeedItem.Post -> PostView(item)
                is FeedItem.Ad -> AdView(item)
                is FeedItem.Suggested -> SuggestedView(item)
            }
        }
    }
}

// Měření výkonu pomocí listState
@Composable
fun ScrollPerformance(listState: LazyListState) {
    val scrollInfo = listState.layoutInfo
    val visibleCount = scrollInfo.visibleItemsInfo.size
    val total = scrollInfo.totalItemsCount

    // derivedStateOf — nepřekompovává se, pokud se hodnota nezměnila
    val scrollProgress by remember {
        derivedStateOf {
            if (total > 0) visibleCount.toFloat() / total else 0f
        }
    }

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

Běžné chyby: (1) chybějící key — prvky se překompovávají při každé změně seznamu; (2) použití mutableStateListOf bez snapshot toků — změny nemusí být sledovány; (3) volání suspend funkcí uvnitř itemContent — přerušuje kompozici; (4) bez contentType pro smíšené typy — Compose znovu používá kompozici reklamy pro textový příspěvek, což způsobuje vizuální artefakty. V IT Sectr přidáváme contentType všem seznamům se třemi a více typy obsahu.

Často kladené otázky

LazyColumn se posouvá pomalu — co dělat?

Zkontrolujte tři parametry: (1) key — bez stabilního klíče Compose nemůže znovu použít kompozici, (2) contentType — pro seznamy s různými typy prvků, (3) beyondBoundsPageCount — zvyšte na 2 pro obrázky. Těžké výpočty uvnitř itemContent přesuňte do remember s klíčem podle dat. Použijte Modifier.drawWithContent místo Image pro jednoduchou grafiku.

Jak přidat oddělovač mezi prvky LazyColumn?

Použijte LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) pro mezery mezi prvky. Pro vizuální oddělovač s čárou přidejte item { Divider() } mezi items. Automatický oddělovač: items(items, key = { it.id }) { item -> ... }, a mezi každý prvek vložte item { HorizontalDivider() }. K tomu použijte LazyListScope.items(list, key) { /* prvek */ } a samostatně LazyListScope.item { Divider() }.

LazyColumn uvnitř Column nefunguje — proč?

LazyColumn má ve výchozím nastavení nekonečnou výšku (fillMaxSize). Uvnitř Column bez pevné výšky nemůže LazyColumn určit velikost. Řešení: (1) nastavte LazyColumn pevnou výšku: Modifier.height(400.dp), (2) použijte Modifier.weight(1f) uvnitř Column, (3) nevkládejte LazyColumn do vertikálně posouvatelného kontejneru — použijte LazyColumn jako kořenový prvek s item {} pro header a footer.

Jak aktualizovat LazyColumn při změně dat?

LazyColumn automaticky reaguje na změny State. Pokud jsou data uložena v mutableStateListOf nebo mutableStateOf, Compose překompovává pouze změněné prvky. Pro seznamy z ViewModel použijte collectAsState() s Flow. Při aktualizaci seznamu s key Compose automaticky animuje změny (přidání, odstranění, přesun) prostřednictvím Modifier.animateItemPlacement().

LazyColumn vs LazyVerticalGrid — co vybrat?

LazyColumn — pro seznamy s jedním sloupcem (feed, chat, komentáře). LazyVerticalGrid — pro mřížky (galerie, katalog, ikony). Pokud mají být prvky v telefonu v jednom sloupci a na tabletu ve dvou — použijte LazyVerticalGrid s GridCells.Adaptive. Pokud potřebujete smíšenou mřížku (různý počet sloupců v jedné sekci) — použijte LazyColumn a uvnitř items vložte horizontální LazyRow nebo vnořené LazyVerticalGrid.

Shrnutí

  • LazyColumn — komponenta Jetpack Compose pro líné vertikální seznamy s deklarativním API.
  • items() API přijímá seznam a DSL blok pro každý prvek; key je povinný pro výkon.
  • LazyListState spravuje pozici posouvání, poskytuje suspend funkce pro programové posouvání.
  • contentType odděluje kompoziční pooly pro seznamy s různými typy prvků.
  • LazyVerticalGrid / LazyRow — varianty pro mřížky a horizontální seznamy se stejným items API.
  • LazyColumn poskytuje 60 FPS pro 10000+ prvků díky SubcomposeLayout a vyrovnávací paměti prefetch.
  • Pro nové Compose projekty používejte LazyColumn; pro View-based projekty — RecyclerView.

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také