LazyColumn — Jetpack Compose-komponent för att visa rullningsbara listor med lat lastning. Till skillnad från RecyclerView skapar LazyColumn inga ViewHolders och använder inte XML — alla element beskrivs deklarativt genom composable-funktioner. Vi visar vad LazyColumn är, hur LazyListState och items API fungerar, och hur man uppnår prestanda på 60 FPS. Hos IT Sectr använder vi LazyColumn i alla nya Compose-projekt. För jämförelse med det klassiska tillvägagångssättet, läs artikeln om RecyclerView.
Huvudpunkter
items(), itemsIndexed(), items() med nycklar för databindning.LazyColumn — är en composable-funktion från Foundation Compose-biblioteket som visar en vertikal lista med element med lat (lazy) lastning: endast element som är synliga på skärmen eller i närheten (prefetch-zon) skapas och komponeras. LazyColumn dök upp i Compose 1.0 (2021) tillsammans med LazyRow (horisontell lista) och LazyVerticalGrid (rutnät).
Lat lastning fungerar genom mekanismen SubcomposeLayout: LazyColumn mäter tillgängligt utrymme, begär den synliga räckvidden från LayoutInfo och komponerar endast element i den räckvidden. Element som har lämnat skärmen lämnar kompositionen (förutom prefetch-bufferten). Enligt Google-data (Android Performance, 2026) ger LazyColumn 60 FPS vid rullning av en lista med 10000+ element på medelklass-enheter.
Column med vertikal rullning — är vanlig placering av alla element under varandra med tillämpning av verticalScroll-modifieraren. Column är inte lat: alla underordnade element komponeras omedelbart, även om de inte är synliga. För en lista med 200+ element orsakar Column betydande fördröjning vid start. LazyColumn komponerar endast synliga element + buffert (som standard 1 skärm framåt/bakåt), vilket gör den oumbärlig för långa listor.
items API — uppsättning tilläggsfunktioner för LazyColumn som accepterar en datalista och en DSL-beskrivning för varje element. Huvudfunktioner: items(count, key, itemContent) för fast antal, items(list, key, itemContent) för lista, itemsIndexed(list, key, itemContent) med index. Parametern key — obligatoriskt villkor för prestanda — ger Compose en stabil identifierare för elementet.
// Grundläggande LazyColumn med itemContent
@Composable
fun ArticleList(articles: List<Article>) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// Header — inte lat, alltid i början
item {
Text("Senaste artiklarna", style = MaterialTheme.typography.headlineMedium)
}
// Lista över artiklar med nyckel på id
items(articles, key = { it.id }) { article ->
ArticleCard(
title = article.title,
summary = article.summary,
onClick = { onArticleClick(article.id) }
)
}
// Footer med laddningsindikator
item {
CircularProgressIndicator(
modifier = Modifier.fillMaxWidth().padding(16.dp)
)
}
}
}
// Anpassat listelement
@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(): använd item {} för enskilda element som alltid finns i listan (header, footer, avskiljare). Använd items() för dynamisk datalista. items() accepterar Iterable eller Int och ett DSL-block som anropas för varje element. Kombinera item och items i samma LazyColumn: anropsordningen bestämmer visningsordningen.
LazyListState — objekt som lagrar liststatus: aktuell rullningsposition (firstVisibleItemIndex, firstVisibleItemScrollOffset), synliga element (layoutInfo) och metoder för programmerbar rullning (scrollToItem, animateScrollToItem). LazyListState skapas via rememberLazyListState() och skickas till LazyColumn via parametern state.
// LazyColumn med bevarad rullningsposition
@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)
}
}
// Knappen "Upp" visas efter 10:e elementet
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, "Till toppen")
}
}
}
}
// Hämta information om synliga element
@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("Visar $firstItem–$lastItem av $totalItems")
}
Programmerbar rullning utförs genom suspend-funktioner i listState: scrollToItem(index, scrollOffset) — omedelbar förflyttning (utan animering), animateScrollToItem(index) — med animering. För rullning från en korutin, använd LaunchedEffect eller coroutineScope.launch. Viktigt: anropa inte suspend-funktioner från komposition (inuti @Composable) — använd lambdas (onClick, LaunchedEffect).
LazyColumn och RecyclerView löser samma uppgift — effektiv visning av långa listor. Skillnaden är i arkitekturen: RecyclerView använder ViewHolder och Adapter (klassisk Android View), LazyColumn — deklarativa composable-funktioner utan XML och ViewHolder. Valet beror på projektets teknologistack: Compose vs View-based UI.
| Parameter | LazyColumn (Compose) | RecyclerView (View) |
|---|---|---|
| Layout | Composable-funktioner (Kotlin DSL) | XML-filer + ViewBinding |
| Adapter | items() / itemsIndexed() DSL | RecyclerView.Adapter + ViewHolder |
| Sortering/filtrering | Snapshot state + derivedStateOf | DiffUtil + AsyncListDiffer |
| Animering | AnimatedVisibility + Modifier.animateItem | DefaultItemAnimator |
| Olika elementtyper | item {}/items() + when efter typ | getItemViewType + flera ViewHolders |
| Rullning till position | listState.animateScrollToItem() | layoutManager.scrollToPosition() |
| Prefetch | In-process (SubcomposeLayout-buffert) | RecycledViewPool + GapWorker |
| Nätverk/paginering | collectAsLazyPagingItems() + Paging 3 | PagingDataAdapter + Paging 3 |
Googles rekommendation (Android Developers, 2026): för nya projekt på Jetpack Compose, använd LazyColumn. För View-based projekt, använd RecyclerView. LazyColumn kräver Compose, vilket lägger till cirka 3–5 MB i APK — ta hänsyn till detta vid support av äldre enheter. Om projektet redan använder View-systemet kan LazyColumn inte samexistera på samma skärm med XML-layout utan AndroidView — i sådana fall är det enklare att stanna kvar på RecyclerView.
LazyVerticalGrid — version av LazyColumn för rutnät med fast antal kolumner (columns). LazyHorizontalGrid — horisontellt rutnät med fast antal rader (rows). Båda komponenterna använder samma principer för lat lastning och samma items API som LazyColumn. Parametern columns bestämmer antalet kolumner via GridCells.Fixed(N) eller GridCells.Adaptive(minSize).
// Rutnät med adaptiva kolumner
@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)
)
}
}
}
// Horisontell 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 beräknar automatiskt antalet kolumner baserat på minimistorleken på cellen. Till exempel, GridCells.Adaptive(128.dp) på en skärm med bredden 360 dp placerar 2 kolumner, på en surfplatta 600 dp — 4 kolumner. GridCells.Fixed(2) visar alltid exakt 2 kolumner. Adaptive är att föredra för multi-skärm-anpassning — den anpassar sig automatiskt till skärmbredden utan media-frågor.
Prestanda för LazyColumn beror på tre faktorer: stabilitet hos nycklar (key), innehållstyp (contentType) och prefetch-buffert (beyondBounds). Som standard buffrar LazyColumn 1 skärm framåt och 1 bakåt. Google (Android Developers, 2026) rekommenderar att konfigurera dessa parametrar för listor med olika elementtyper eller komplex komposition.
| Parameter | Beskrivning | Rekommendation |
|---|---|---|
| key | Stabil identifierare för element | Ange alltid key = { it.id }. Utan key använder Compose index och omordning av element förstör animeringen. |
| contentType | Innehållstyp för separering av pooler | Ange för listor med olika elementtyper (text + bild + annons). Compose återanvänder komposition endast inom samma contentType. |
| beyondBounds | Antal skärmar för prefetch | Öka till 2–3 för listor med tunga bilder. Standard 1 skärm. |
| Modifier.animateItem | Animering av positionsändring | Använd för animerad sortering och filtrering (Compose 1.7+). |
// Optimerad LazyColumn med contentType och prefetch
@Composable
fun OptimizedFeed(items: List<FeedItem>) {
val listState = rememberLazyListState()
LazyColumn(
state = listState,
modifier = Modifier.fillMaxSize(),
beyondBoundsPageCount = 2 // prefetch 2 skärmar
) {
items(
items = items,
key = { it.id },
contentType = { it.type } // separering efter typ
) { item ->
when (item) {
is FeedItem.Post -> PostView(item)
is FeedItem.Ad -> AdView(item)
is FeedItem.Suggested -> SuggestedView(item)
}
}
}
}
// Prestandamätning via listState
@Composable
fun ScrollPerformance(listState: LazyListState) {
val scrollInfo = listState.layoutInfo
val visibleCount = scrollInfo.visibleItemsInfo.size
val total = scrollInfo.totalItemsCount
// derivedStateOf — komponeras inte om om värdet inte har ändrats
val scrollProgress by remember {
derivedStateOf {
if (total > 0) visibleCount.toFloat() / total else 0f
}
}
LinearProgressIndicator(
progress = scrollProgress,
modifier = Modifier.fillMaxWidth().height(2.dp)
)
}
Vanliga misstag: (1) avsaknad av key — element komponeras om vid varje ändring av listan; (2) användning av mutableStateListOf utan snapshot-strömmar — ändringar kan inte spåras; (3) anrop av suspend-funktioner inuti itemContent — avbryter komposition; (4) utan contentType för blandade typer — Compose återanvänder annonskomposition för textinlägg, vilket orsakar visuella artefakter. Hos IT Sectr lägger vi till contentType i alla listor med tre eller fler innehållstyper.
Vanliga frågor
Kontrollera tre parametrar: (1) key — utan stabil nyckel kan Compose inte återanvända komposition, (2) contentType — för listor med olika elementtyper, (3) beyondBoundsPageCount — öka till 2 för bilder. Flytta tunga beräkningar inuti itemContent till remember med nyckel baserad på data. Använd Modifier.drawWithContent istället för Image för enkel grafik.
Använd LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) för mellanrum mellan element. För visuell avskiljare med linje, lägg till item { Divider() } mellan items. Automatisk avskiljare: items(items, key = { it.id }) { item -> ... }, och mellan varje element sätt in item { HorizontalDivider() }. För detta använd LazyListScope.items(list, key) { /* element */ } och separat LazyListScope.item { Divider() }.
LazyColumn har oändlig höjd (fillMaxSize) som standard. Inuti Column utan fast höjd kan LazyColumn inte bestämma storlek. Lösning: (1) ge LazyColumn fast höjd: Modifier.height(400.dp), (2) använd Modifier.weight(1f) inuti Column, (3) placera inte LazyColumn i en vertikalt rullningsbar behållare — använd LazyColumn som rot-element med item {} för header och footer.
LazyColumn reagerar automatiskt på ändringar i State. Om data lagras i mutableStateListOf eller mutableStateOf komponerar Compose om endast ändrade element. För listor från ViewModel, använd collectAsState() med Flow. Vid uppdatering av listan med key animerar Compose automatiskt ändringar (tillägg, borttagning, flyttning) genom Modifier.animateItemPlacement().
LazyColumn — för listor med en kolumn (feed, chatt, kommentarer). LazyVerticalGrid — för rutnät (galleri, katalog, ikoner). Om element ska vara i en kolumn på telefonen och två kolumner på surfplattan — använd LazyVerticalGrid med GridCells.Adaptive. Om du behöver ett blandat rutnät (olika antal kolumner i en sektion) — använd LazyColumn och inuti items sätt in horisontell LazyRow eller nästlade LazyVerticalGrid.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också