LazyColumn — qu'est-ce que c'est, listes efficaces dans Compose

Auteur : IT Sectr Publié le : 2026-02-24 Temps de lecture : 10 min

LazyColumn — un composant Jetpack Compose pour afficher des listes défilables avec chargement paresseux. Contrairement à RecyclerView, LazyColumn ne crée pas de ViewHolders et n'utilise pas XML — tous les éléments sont décrits de manière déclarative via des fonctions composables. Nous montrons ce qu'est LazyColumn, comment fonctionnent LazyListState et l'API items, et comment atteindre des performances de 60 FPS. Chez IT Sectr, nous utilisons LazyColumn dans tous les nouveaux projets Compose. Pour une comparaison avec l'approche classique, lisez l'article sur RecyclerView.

Points clés

  • LazyColumn — un composant Jetpack Compose pour les listes verticales avec chargement paresseux, faisant partie de Material Design 3.
  • LazyListState — état de la liste : position de défilement, éléments visibles, défilement programmatique.
  • items API — fonctions items(), itemsIndexed(), items() avec clés pour la liaison de données.
  • key — paramètre pour l'identification stable des éléments, permettant à Compose de réutiliser la composition.
  • LazyVerticalGrid — version de LazyColumn pour les grilles avec un nombre fixe de colonnes.

Qu'est-ce que LazyColumn dans Jetpack Compose ?

LazyColumn est une fonction composable de la bibliothèque Foundation Compose qui affiche une liste verticale d'éléments avec chargement paresseux : seuls les éléments visibles à l'écran ou à proximité (zone de préchargement) sont créés et composés. LazyColumn est apparu dans Compose 1.0 (2021) avec LazyRow (liste horizontale) et LazyVerticalGrid (grille).

Le chargement paresseux fonctionne via le mécanisme SubcomposeLayout : LazyColumn mesure l'espace disponible, demande la plage visible à LayoutInfo et compose uniquement les éléments de cette plage. Les éléments qui sortent de l'écran quittent la composition (sauf le tampon de préchargement). Selon Google (Android Performance, 2026), LazyColumn maintient 60 FPS lors du défilement d'une liste de 10000+ éléments sur des appareils de gamme moyenne.

Différence avec Column + ScrollState

Column avec défilement vertical est un agencement normal de tous les éléments les uns sous les autres avec le modificateur verticalScroll appliqué. Column n'est pas paresseuse : tous les éléments enfants sont composés immédiatement, même s'ils ne sont pas visibles. Pour une liste de 200+ éléments, Column provoque un décalage important au démarrage. LazyColumn compose uniquement les éléments visibles + tampon (1 écran avant/arrière par défaut), ce qui le rend indispensable pour les longues listes.

items API : liaison des données à la liste

items API est un ensemble de fonctions d'extension pour LazyColumn qui acceptent une liste de données et un descripteur DSL pour chaque élément. Les principales fonctions sont : items(count, key, itemContent) pour un nombre fixe, items(list, key, itemContent) pour une liste, itemsIndexed(list, key, itemContent) avec index. Le paramètre key est obligatoire pour les performances — il donne à Compose un identifiant d'élément stable.

kotlin
// LazyColumn de base avec itemContent
@Composable
fun ArticleList(articles: List<Article>) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        // Header — pas paresseux, toujours au début
        item {
            Text("Derniers articles", style = MaterialTheme.typography.headlineMedium)
        }

        // Liste d'articles avec clé par id
        items(articles, key = { it.id }) { article ->
            ArticleCard(
                title = article.title,
                summary = article.summary,
                onClick = { onArticleClick(article.id) }
            )
        }

        // Footer avec indicateur de chargement
        item {
            CircularProgressIndicator(
                modifier = Modifier.fillMaxWidth().padding(16.dp)
            )
        }
    }
}

// Élément de liste personnalisé
@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() : utilisez item {} pour les éléments uniques toujours présents dans la liste (en-tête, pied de page, séparateurs). Utilisez items() pour une liste de données dynamique. items() accepte un Iterable ou Int et un bloc DSL appelé pour chaque élément. Combinez item et items dans un même LazyColumn : l'ordre des appels détermine l'ordre d'affichage.

LazyListState : gestion de la position de défilement

LazyListState est un objet qui stocke l'état de la liste : position actuelle du défilement (firstVisibleItemIndex, firstVisibleItemScrollOffset), éléments visibles (layoutInfo) et méthodes de défilement programmatique (scrollToItem, animateScrollToItem). LazyListState est créé via rememberLazyListState() et transmis à LazyColumn via le paramètre state.

kotlin
// LazyColumn avec préservation de la position de défilement
@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)
            }
        }

        // Bouton "Vers le haut" apparaît après le 10e élément
        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, "Vers le haut")
            }
        }
    }
}

// Obtention d'informations sur les éléments visibles
@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("Affichage $firstItem–$lastItem sur $totalItems")
}

Le défilement programmatique s'effectue via les fonctions suspend de listState : scrollToItem(index, scrollOffset) — déplacement instantané (sans animation), animateScrollToItem(index) — avec animation. Pour le défilement depuis une coroutine, utilisez LaunchedEffect ou coroutineScope.launch. Important : n'appelez pas de fonctions suspend depuis la composition (dans @Composable) — utilisez des lambdas (onClick, LaunchedEffect).

LazyColumn vs RecyclerView : comparaison des approches

LazyColumn et RecyclerView résolvent le même problème — l'affichage efficace de longues listes. La différence réside dans l'architecture : RecyclerView utilise ViewHolder et Adapter (vue Android classique), LazyColumn utilise des fonctions composables déclaratives sans XML ni ViewHolder. Le choix dépend de la pile technologique du projet : Compose vs UI basée sur View.

ParamètreLazyColumn (Compose)RecyclerView (View)
DispositionFonctions composables (Kotlin DSL)Fichiers XML + ViewBinding
Adaptateuritems() / itemsIndexed() DSLRecyclerView.Adapter + ViewHolder
Tri/FiltrageSnapshot state + derivedStateOfDiffUtil + AsyncListDiffer
AnimationAnimatedVisibility + Modifier.animateItemDefaultItemAnimator
Différents types d'élémentsitem {}/items() + when par typegetItemViewType + plusieurs ViewHolders
Défiler vers une positionlistState.animateScrollToItem()layoutManager.scrollToPosition()
PréchargementEn processus (tampon SubcomposeLayout)RecycledViewPool + GapWorker
Réseau/PaginationcollectAsLazyPagingItems() + Paging 3PagingDataAdapter + Paging 3

Recommandation de Google (Android Developers, 2026) : pour les nouveaux projets sur Jetpack Compose, utilisez LazyColumn. Pour les projets basés sur View, utilisez RecyclerView. LazyColumn nécessite Compose, qui ajoute environ 3–5 Mo à l'APK — tenez-en compte lors du support des appareils anciens. Si un projet utilise déjà le système View, LazyColumn ne peut pas coexister sur un même écran avec une mise en page XML sans AndroidView — dans ces cas, il est plus simple de rester avec RecyclerView.

LazyVerticalGrid et LazyHorizontalGrid : grilles

LazyVerticalGrid est une version de LazyColumn pour les grilles avec un nombre fixe de colonnes. LazyHorizontalGrid est une grille horizontale avec des lignes fixes. Les deux composants utilisent les mêmes principes de chargement paresseux et la même API items que LazyColumn. Le paramètre columns détermine le nombre de colonnes via GridCells.Fixed(N) ou GridCells.Adaptive(minSize).

kotlin
// Grille avec colonnes adaptatives
@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)
            )
        }
    }
}

// Liste horizontale (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 calcule automatiquement le nombre de colonnes en fonction de la taille minimale de la cellule. Par exemple, GridCells.Adaptive(128.dp) sur un écran de 360 dp de large place 2 colonnes, sur une tablette de 600 dp — 4 colonnes. GridCells.Fixed(2) affiche toujours exactement 2 colonnes. Adaptive est préférable pour l'adaptation multi-écran — il s'ajuste automatiquement à la largeur de l'écran sans requêtes média.

Performances de LazyColumn : key, contentType, prefetch

Les performances de LazyColumn dépendent de trois facteurs : la stabilité de la clé (key), le type de contenu (contentType) et le tampon de préchargement (beyondBounds). Par défaut, LazyColumn met en tampon 1 écran avant et 1 arrière. Google (Android Developers, 2026) recommande de configurer ces paramètres pour les listes avec différents types d'éléments ou une composition complexe.

ParamètreDescriptionRecommandation
keyIdentifiant stable d'élémentSpécifiez toujours key = { it.id }. Sans key, Compose utilise l'index, et le réordonnancement des éléments casse l'animation.
contentTypeType de contenu pour la séparation des poolsSpécifiez pour les listes avec différents types d'éléments (texte + image + publicité). Compose ne réutilisera la composition qu'au sein du même contentType.
beyondBoundsNombre d'écrans de préchargementAugmentez à 2–3 pour les listes avec des images lourdes. Par défaut, 1 écran.
Modifier.animateItemAnimation de changement de positionUtilisez pour le tri et le filtrage animés (Compose 1.7+).
kotlin
// LazyColumn optimisée avec contentType et prefetch
@Composable
fun OptimizedFeed(items: List<FeedItem>) {
    val listState = rememberLazyListState()

    LazyColumn(
        state = listState,
        modifier = Modifier.fillMaxSize(),
        beyondBoundsPageCount = 2  // préchargement de 2 écrans
    ) {
        items(
            items = items,
            key = { it.id },
            contentType = { it.type }  // division par type
        ) { item ->
            when (item) {
                is FeedItem.Post -> PostView(item)
                is FeedItem.Ad -> AdView(item)
                is FeedItem.Suggested -> SuggestedView(item)
            }
        }
    }
}

// Mesure des performances via listState
@Composable
fun ScrollPerformance(listState: LazyListState) {
    val scrollInfo = listState.layoutInfo
    val visibleCount = scrollInfo.visibleItemsInfo.size
    val total = scrollInfo.totalItemsCount

    // derivedStateOf — ne recompose pas si la valeur n'a pas changé
    val scrollProgress by remember {
        derivedStateOf {
            if (total > 0) visibleCount.toFloat() / total else 0f
        }
    }

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

Erreurs courantes : (1) absence de key — les éléments sont recomposés à tout changement de liste ; (2) utilisation de mutableStateListOf sans flux snapshot — les changements peuvent ne pas être suivis ; (3) appel de fonctions suspend dans itemContent — interrompt la composition ; (4) sans contentType pour les types mixtes — Compose réutilise la composition des publicités pour les publications textuelles, provoquant des artefacts visuels. Chez IT Sectr, nous ajoutons contentType à toutes les listes ayant trois types de contenu ou plus.

Questions fréquentes

LazyColumn défile lentement — que faire ?

Vérifiez trois paramètres : (1) key — sans clé stable, Compose ne peut pas réutiliser la composition, (2) contentType — pour les listes avec différents types d'éléments, (3) beyondBoundsPageCount — augmentez à 2 pour les images. Déplacez les calculs lourds dans itemContent vers remember avec une clé par données. Utilisez Modifier.drawWithContent au lieu de Image pour les graphiques simples.

Comment ajouter un séparateur entre les éléments de LazyColumn ?

Utilisez LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) pour l'espacement entre les éléments. Pour un séparateur visuel avec une ligne, ajoutez item { Divider() } entre les items. Séparateur automatique : items(items, key = { it.id }) { item -> ... }, et entre chaque élément insérez item { HorizontalDivider() }. Pour cela, utilisez LazyListScope.items(list, key) { /* élément */ } et séparément LazyListScope.item { Divider() }.

LazyColumn dans Column ne fonctionne pas — pourquoi ?

LazyColumn a une hauteur infinie (fillMaxSize) par défaut. Dans une Column sans hauteur fixe, LazyColumn ne peut pas déterminer sa taille. Solution : (1) définissez une hauteur fixe sur LazyColumn : Modifier.height(400.dp), (2) utilisez Modifier.weight(1f) dans Column, (3) n'imbriquez pas LazyColumn dans un conteneur à défilement vertical — utilisez LazyColumn comme élément racine avec item {} pour l'en-tête et le pied de page.

Comment mettre à jour LazyColumn lors du changement de données ?

LazyColumn réagit automatiquement aux changements d'état. Si les données sont stockées dans mutableStateListOf ou mutableStateOf, Compose ne recompose que les éléments modifiés. Pour les listes provenant de ViewModel, utilisez collectAsState() avec Flow. Lors de la mise à jour de la liste avec key, Compose anime automatiquement les changements (ajout, suppression, réorganisation) via Modifier.animateItemPlacement().

LazyColumn vs LazyVerticalGrid — lequel choisir ?

LazyColumn — pour les listes à une colonne (fil d'actualité, chat, commentaires). LazyVerticalGrid — pour les grilles (galerie, catalogue, icônes). Si les éléments doivent être en une colonne sur téléphone et en deux sur tablette — utilisez LazyVerticalGrid avec GridCells.Adaptive. Si une grille mixte est nécessaire (nombre de colonnes différent dans une section) — utilisez LazyColumn et insérez LazyRow horizontal ou LazyVerticalGrid imbriqué dans les items.

Résumé

  • LazyColumn — un composant Jetpack Compose pour les listes verticales paresseuses avec une API déclarative.
  • items() API accepte une liste et un bloc DSL pour chaque élément ; key est obligatoire pour les performances.
  • LazyListState gère la position de défilement, fournit des fonctions suspend pour le défilement programmatique.
  • contentType sépare les pools de composition pour les listes avec différents types d'éléments.
  • LazyVerticalGrid / LazyRow — variantes pour grilles et listes horizontales avec la même API items.
  • LazyColumn maintient 60 FPS pour 10000+ éléments grâce à SubcomposeLayout et au tampon de préchargement.
  • Pour les nouveaux projets Compose, utilisez LazyColumn ; pour les projets basés sur View — RecyclerView.

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi