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 και συνθέτει μόνο τα στοιχεία σε αυτό το εύρος. Τα στοιχεία που έχουν φύγει από την οθόνη εγκαταλείπουν τη σύνθεση (εκτός από το buffer prefetch). Σύμφωνα με δεδομένα της Google (Android Performance, 2026), το LazyColumn παρέχει 60 FPS κατά την κύλιση λίστας 10000+ στοιχείων σε συσκευές μεσαίας κατηγορίας.

Διαφορά με Column + ScrollState

Column με κάθετη κύλιση — είναι η συνήθης τοποθέτηση όλων των στοιχείων το ένα κάτω από το άλλο με εφαρμογή του τροποποιητή verticalScroll. Το Column δεν είναι τεμπέλικο: όλα τα θυγατρικά στοιχεία συντίθενται αμέσως, ακόμα κι αν δεν είναι ορατά. Για λίστα 200+ στοιχείων, το Column προκαλεί σημαντική καθυστέρηση κατά την εκκίνηση. Το LazyColumn συνθέτει μόνο τα ορατά στοιχεία + buffer (από προεπιλογή 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) — με κινούμενα σχέδια. Για κύλιση από coroutine χρησιμοποιήστε LaunchedEffect ή coroutineScope.launch. Σημαντικό: μην καλείτε συναρτήσεις suspend από τη σύνθεση (εντός @Composable) — χρησιμοποιήστε lambdas (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
Adapteritems() / itemsIndexed() DSLRecyclerView.Adapter + ViewHolder
Ταξινόμηση/φιλτράρισμαSnapshot state + derivedStateOfDiffUtil + AsyncListDiffer
Κινούμενα σχέδιαAnimatedVisibility + Modifier.animateItemDefaultItemAnimator
Διαφορετικοί τύποι στοιχείωνitem {}/items() + when ανά τύποgetItemViewType + πολλαπλά ViewHolder
Κύλιση σε θέσηlistState.animateScrollToItem()layoutManager.scrollToPosition()
PrefetchIn-process (buffer SubcomposeLayout)RecycledViewPool + GapWorker
Δίκτυο/σελιδοποίησηcollectAsLazyPagingItems() + Paging 3PagingDataAdapter + Paging 3

Σύσταση Google (Android Developers, 2026): για νέα έργα σε Jetpack Compose χρησιμοποιήστε LazyColumn. Για View-based έργα χρησιμοποιήστε RecyclerView. Το LazyColumn απαιτεί Compose, το οποίο προσθέτει περίπου 3–5 MB στο APK — λάβετε αυτό υπόψη κατά την υποστήριξη παλαιών συσκευών. Εάν το έργο χρησιμοποιεί ήδη σύστημα 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 στήλες, σε tablet 600 dp — 4 στήλες. GridCells.Fixed(2) εμφανίζει πάντα ακριβώς 2 στήλες. Το Adaptive είναι προτιμότερο για προσαρμογή πολλαπλών οθονών — προσαρμόζεται αυτόματα στο πλάτος οθόνης χωρίς ερωτήματα πολυμέσων.

Απόδοση LazyColumn: key, contentType, prefetch

Απόδοση του LazyColumn εξαρτάται από τρεις παράγοντες: σταθερότητα κλειδιών (key), τύπος περιεχομένου (contentType) και buffer 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 as ριζικό στοιχείο με item {} για header και footer.

Πώς να ενημερώσω το LazyColumn όταν αλλάζουν τα δεδομένα;

LazyColumn αντιδρά αυτόματα σε αλλαγές State. Εάν τα δεδομένα αποθηκεύονται σε mutableStateListOf ή mutableStateOf, το Compose επανασυνθέτει μόνο τα στοιχεία που άλλαξαν. Για λίστες από ViewModel χρησιμοποιήστε collectAsState() με Flow. Κατά την ενημέρωση λίστας με key, το Compose κάνει αυτόματα κινούμενα σχέδια αλλαγών (προσθήκη, αφαίρεση, μετακίνηση) μέσω Modifier.animateItemPlacement().

LazyColumn vs LazyVerticalGrid — τι να επιλέξω;

LazyColumn — για λίστες μίας στήλης (feed, chat, σχόλια). LazyVerticalGrid — για πλέγματα (γαλλερία, κατάλογος, εικονίδια). Εάν τα στοιχεία πρέπει να είναι σε μία στήλη στο τηλέφωνο και δύο στήλες στο tablet — χρησιμοποιήστε 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 και το buffer prefetch.
  • Για νέα έργα Compose χρησιμοποιήστε LazyColumn; για View-based έργα — RecyclerView.

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης