LazyColumn — шта је то, ефикасне листе у Compose-у

Аутор: IT Sectr Објављено: 2026-02-24 Време читања: 10 мин

LazyColumn — компонента Jetpack Compose-а за приказ померајућих листи са лењим учитавањем. За разлику од RecyclerView-а, LazyColumn не креира ViewHolder-е и не користи XML — сви елементи се описују декларативно кроз composable функције. Показујемо шта је LazyColumn, како раде LazyListState и items API, и како постићи перформансе на нивоу 60 FPS. У IT Sectr-у користимо LazyColumn у свим новим Compose пројектима. За поређење са класичним приступом прочитајте чланак о RecyclerView-у.

Главно

  • LazyColumn — компонента Jetpack Compose-а за вертикалне листе са лењим учитавањем, део Material Design 3.
  • LazyListState — стање листе: позиција померања, видљиви елементи, програмско померање.
  • items API — функције items(), itemsIndexed(), items() са кључевима за повезивање података.
  • key — параметар за стабилну идентификацију елемената, омогућава Compose-у да поново користи композицију.
  • LazyVerticalGrid — верзија LazyColumn-а за мреже са фиксним бројем колона.

Шта је LazyColumn у Jetpack Compose-у?

LazyColumn — composable функција из библиотеке Foundation Compose која приказује вертикалну листу елемената са лењим (lazy) учитавањем: креирају се и компонују само они елементи који су видљиви на екрану или се налазе у близини (prefetch зона). LazyColumn се појавио у Compose 1.0 (2021) заједно са LazyRow (хоризонтална листа) и LazyVerticalGrid (мрежа).

Лењо учитавање ради кроз механизам SubcomposeLayout: LazyColumn мери доступан простор, захтева видљиви опсег од LayoutInfo-а и компонује само елементе у том опсегу. Елементи који су изашли са екрана напуштају композицију (осим prefetch бафера). Према Google подацима (Android Performance, 2026), LazyColumn обезбеђује 60 FPS при померању листе од 10000+ елемената на уређајима средњег сегмента.

Разлика са Column + ScrollState

Column са вертикалним померањем — обичан распоред свих елемената један испод другог са применом модификатора verticalScroll. Column није лењ: сви подређени елементи се компонују одмах, чак и ако нису видљиви. За листу од 200+ елемената Column изазива значајно кашњење при покретању. LazyColumn компонује само видљиве + бафер (подразумевано 1 екран напред/назад), што га чини незаменљивим за дугачке листе.

items API: повезивање података са листом

items API — скуп екстензија за LazyColumn које примају листу података и DSL описивач за сваки елемент. Главне функције: items(count, key, itemContent) за фиксни број, items(list, key, itemContent) за листу, itemsIndexed(list, key, itemContent) са индексом. Параметар key — обавезан услов за перформансе — даје Compose-у стабилан идентификатор елемента.

kotlin
// Основна LazyColumn са itemContent
@Composable
fun ArticleList(articles: List<Article>) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        // Header — није лењ, увек на почетку
        item {
            Text("Последњи чланци", style = MaterialTheme.typography.headlineMedium)
        }

        // Листа чланака са кључем по id
        items(articles, key = { it.id }) { article ->
            ArticleCard(
                title = article.title,
                summary = article.summary,
                onClick = { onArticleClick(article.id) }
            )
        }

        // Footer са индикатором учитавања
        item {
            CircularProgressIndicator(
                modifier = Modifier.fillMaxWidth().padding(16.dp)
            )
        }
    }
}

// Прилагођени елемент листе
@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(): користите item {} за појединачне елементе који су увек присутни у листи (header, footer, раздвајачи). Користите items() за динамичку листу података. items() прихвата Iterable или Int и DSL блок који се позива за сваки елемент. Комбинујте item и items у једном LazyColumn-у: редослед позива одређује редослед приказа.

LazyListState: управљање позицијом померања

LazyListState — објекат који чува стање листе: тренутну позицију померања (firstVisibleItemIndex, firstVisibleItemScrollOffset), видљиве елементе (layoutInfo) и методе програмског померања (scrollToItem, animateScrollToItem). LazyListState се креира кроз rememberLazyListState() и прослеђује LazyColumn-у кроз параметар state.

kotlin
// 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)
            }
        }

        // Дугме "Горе" се појављује после 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, "На врх")
            }
        }
    }
}

// Добијање информација о видљивим елементима
@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 од $totalItems")
}

Програмско померање се извршава кроз suspend функције listState-а: scrollToItem(index, scrollOffset) — тренутно померање (без анимације), animateScrollToItem(index) — са анимацијом. За померање из корутине користите LaunchedEffect или coroutineScope.launch. Важно: не позивајте suspend функције из композиције (унутар @Composable) — користите ламбде (onClick, LaunchedEffect).

LazyColumn vs RecyclerView: поређење приступа

LazyColumn и RecyclerView решавају исти задатак — ефикасан приказ дугачких листи. Разлика је у архитектури: RecyclerView користи ViewHolder и Adapter (класични Android View), LazyColumn — декларативне composable функције без XML-а и ViewHolder-а. Избор зависи од технолошког стека пројекта: Compose vs View-based UI.

ПараметарLazyColumn (Compose)RecyclerView (View)
РаспоредComposable функције (Kotlin DSL)XML фајлови + ViewBinding
Адаптерitems() / itemsIndexed() DSLRecyclerView.Adapter + ViewHolder
Сортирање/филтрирањеSnapshot state + derivedStateOfDiffUtil + AsyncListDiffer
АнимацијаAnimatedVisibility + Modifier.animateItemDefaultItemAnimator
Различити типови елеменатаitem {}/items() + when по типуgetItemViewType + више ViewHolder-а
Померање до позицијеlistState.animateScrollToItem()layoutManager.scrollToPosition()
PrefetchIn-process (SubcomposeLayout бафер)RecycledViewPool + GapWorker
Мрежа/пагинацијаcollectAsLazyPagingItems() + Paging 3PagingDataAdapter + Paging 3

Google препорука (Android Developers, 2026): за нове пројекте на Jetpack Compose-у користите LazyColumn. За View-based пројекте користите RecyclerView. LazyColumn захтева Compose, који додаје у APK око 3–5 MB — узмите ово у обзир при подршци старих уређаја. Ако пројекат већ користи View систем, LazyColumn не може коегзистирати на истом екрану са XML распоредом без AndroidView-а — у таквим случајевима је лакше остати на RecyclerView-у.

LazyVerticalGrid и LazyHorizontalGrid: мреже

LazyVerticalGrid — верзија LazyColumn-а за мреже са фиксним бројем колона (columns). LazyHorizontalGrid — хоризонтална мрежа са фиксним бројем редова (rows). Обе компоненте користе исте принципе лењог учитавања и исти items API као LazyColumn. Параметар columns одређује број колона кроз GridCells.Fixed(N) или GridCells.Adaptive(minSize).

kotlin
// Мрежа са адаптивним колонама
@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)
            )
        }
    }
}

// Хоризонтална листа (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 аутоматски израчунава број колона на основу минималне величине ћелије. На пример, GridCells.Adaptive(128.dp) на екрану ширине 360 dp поставља 2 колоне, на таблету 600 dp — 4 колоне. GridCells.Fixed(2) увек приказује тачно 2 колоне. Adaptive је пожељнији за мултиекранску адаптацију — аутоматски се прилагођава ширини екрана без медија упита.

Перформансе LazyColumn-а: key, contentType, prefetch

Перформансе LazyColumn-а зависе од три фактора: стабилности кључева (key), типа садржаја (contentType) и prefetch бафера (beyondBounds). Подразумевано, LazyColumn баферише 1 екран напред и 1 назад. Google (Android Developers, 2026) препоручује подешавање ових параметара за листе са различитим типовима елемената или сложеном композицијом.

ПараметарОписПрепорука
keyСтабилан идентификатор елементаУвек наведите key = { it.id }. Без key-а Compose користи индекс, а премештање елемената ломи анимацију.
contentTypeТип садржаја за раздвајање пуловаНаведите за листе са различитим типовима елемената (текст + слика + реклама). Compose ће поново користити композицију само унутар истог contentType-а.
beyondBoundsБрој екрана за prefetchПовећајте на 2–3 за листе са тешким сликама. Подразумевано 1 екран.
Modifier.animateItemАнимација промене позицијеКористите за анимирано сортирање и филтрирање (Compose 1.7+).
kotlin
// Оптимизована LazyColumn са contentType и prefetch
@Composable
fun OptimizedFeed(items: List<FeedItem>) {
    val listState = rememberLazyListState()

    LazyColumn(
        state = listState,
        modifier = Modifier.fillMaxSize(),
        beyondBoundsPageCount = 2  // prefetch 2 екрана
    ) {
        items(
            items = items,
            key = { it.id },
            contentType = { it.type }  // подела по типовима
        ) { item ->
            when (item) {
                is FeedItem.Post -> PostView(item)
                is FeedItem.Ad -> AdView(item)
                is FeedItem.Suggested -> SuggestedView(item)
            }
        }
    }
}

// Мерење перформанси кроз listState
@Composable
fun ScrollPerformance(listState: LazyListState) {
    val scrollInfo = listState.layoutInfo
    val visibleCount = scrollInfo.visibleItemsInfo.size
    val total = scrollInfo.totalItemsCount

    // derivedStateOf — не прекомпонује се ако се вредност није променила
    val scrollProgress by remember {
        derivedStateOf {
            if (total > 0) visibleCount.toFloat() / total else 0f
        }
    }

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

Честе грешке: (1) недостатак key-а — елементи се поново компонују при свакој промени листе; (2) коришћење mutableStateListOf без snapshot токова — промене се можда не прате; (3) позивање suspend функција унутар itemContent-а — прекида композицију; (4) без contentType-а за мешане типове — Compose поново користи композицију рекламе за текстуални пост, изазивајући визуелне артефакте. У IT Sectr-у додајемо contentType свим листама са три и више типова садржаја.

Често постављана питања

LazyColumn се споро помера — шта радити?

Проверите три параметра: (1) key — без стабилног кључа Compose не може поново да користи композицију, (2) contentType — за листе са различитим типовима елемената, (3) beyondBoundsPageCount — повећајте на 2 за слике. Тешка израчунавања унутар itemContent-а преместите у remember са кључем по подацима. Користите Modifier.drawWithContent уместо Image-а за једноставну графику.

Како додати раздвајач између елемената LazyColumn-а?

Користите LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) за размаке између елемената. За визуелни раздвајач са линијом додајте item { Divider() } између items. Аутоматски раздвајач: items(items, key = { it.id }) { item -> ... }, а између сваког елемента убаците item { HorizontalDivider() }. За ово користите LazyListScope.items(list, key) { /* елемент */ } и одвојено LazyListScope.item { Divider() }.

LazyColumn унутар Column-а не ради — зашто?

LazyColumn има бесконачну висину (fillMaxSize) подразумевано. Унутар Column-а без фиксне висине, LazyColumn не може да одреди величину. Решење: (1) задајте LazyColumn-у фиксну висину: Modifier.height(400.dp), (2) користите Modifier.weight(1f) унутар Column-а, (3) не стављајте LazyColumn у вертикално помичан контејнер — користите LazyColumn као коренски елемент са item {} за header и footer.

Како ажурирати LazyColumn при промени података?

LazyColumn аутоматски реагује на промене State-а. Ако се подаци чувају у mutableStateListOf или mutableStateOf, Compose поново компонује само измењене елементе. За листе из ViewModel-а користите collectAsState() са Flow-ом. При ажурирању листе са key-ем, Compose аутоматски анимира промене (додавање, уклањање, премештање) кроз Modifier.animateItemPlacement().

LazyColumn vs LazyVerticalGrid — шта изабрати?

LazyColumn — за листе са једном колоном (феед, ћаскање, коментари). LazyVerticalGrid — за мреже (галерија, каталог, иконе). Ако елементи треба да буду у једној колони на телефону и у две на таблету — користите LazyVerticalGrid са GridCells.Adaptive. Ако вам је потребна мешана мрежа (различит број колона у једној секцији) — користите LazyColumn и унутар items убаците хоризонталне LazyRow или угнежђене LazyVerticalGrid.

Закључак

  • LazyColumn — компонента Jetpack Compose-а за лење вертикалне листе са декларативним API-јем.
  • items() API прима листу и DSL блок за сваки елемент; key је обавезан за перформансе.
  • LazyListState управља позицијом померања, пружа suspend функције за програмско померање.
  • contentType раздваја пулове композиције за листе са различитим типовима елемената.
  • LazyVerticalGrid / LazyRow — варијанте за мреже и хоризонталне листе са истим items API-јем.
  • LazyColumn обезбеђује 60 FPS за 10000+ елемената захваљујући SubcomposeLayout-у и prefetch баферу.
  • За нове Compose пројекте користите LazyColumn; за View-based пројекте — RecyclerView.

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође