Elenchi e griglie

Molte app devono mostrare raccolte di elementi. Questo documento spiega come farlo in Jetpack Compose in modo efficiente.

Se sai che il tuo caso d'uso non richiede lo scorrimento, ti consigliamo di utilizzare una semplice Column o Row (a seconda della direzione) ed emettere i contenuti di ciascun elemento ripetendo l'operazione su un elenco come questo:

@Composable
fun MessageList(messages: List<Message>) {
    Column {
        messages.forEach { message ->
            MessageRow(message)
        }
    }
}

Possiamo rendere Column scorrevole usando il modificatore verticalScroll(). Per ulteriori informazioni, consulta la documentazione sui Gesti.

Elenchi inattivi

Se devi visualizzare un numero elevato di elementi (o un elenco di lunghezza sconosciuta), l'utilizzo di un layout come Column può causare problemi di prestazioni, dal momento che tutti gli elementi verranno composti e disposti indipendentemente dal fatto che siano visibili o meno.

Compose fornisce un insieme di componenti che compongono e dispongono solo gli elementi che sono visibili nell'area visibile del componente. Questi componenti includono LazyColumn e LazyRow.

Come suggerisce il nome, la differenza tra LazyColumn e LazyRow è l'orientamento in cui dispongono gli elementi e scorrono. LazyColumn genera un elenco a scorrimento verticale, mentre LazyRow genera un elenco a scorrimento orizzontale.

I componenti Lazy sono diversi dalla maggior parte dei layout di Scrivi. Invece di accettare un parametro di blocco dei contenuti @Composable, che consente alle app di emettere direttamente elementi componibili, i componenti Lazy forniscono un blocco LazyListScope.(). Questo blocco LazyListScope offre una connessione DSL che consente alle app di descrivere i contenuti dell'articolo. Il componente Lazy è quindi responsabile dell'aggiunta dei contenuti di ogni elemento come richiesto dal layout e dalla posizione di scorrimento.

DSL LazyListScope

L'interfaccia DSL di LazyListScope offre una serie di funzioni per la descrizione degli elementi nel layout. In pratica, item() aggiunge un singolo elemento e items(Int) più elementi:

LazyColumn {
    // Add a single item
    item {
        Text(text = "First item")
    }

    // Add 5 items
    items(5) { index ->
        Text(text = "Item: $index")
    }

    // Add another single item
    item {
        Text(text = "Last item")
    }
}

Esistono anche numerose funzioni di estensione che consentono di aggiungere raccolte di elementi, ad esempio un List. Queste estensioni ci consentono di eseguire facilmente la migrazione del nostro Column esempio dall'alto:

/**
 * import androidx.compose.foundation.lazy.items
 */
LazyColumn {
    items(messages) { message ->
        MessageRow(message)
    }
}

Esiste anche una variante della funzione di estensione items() denominata itemsIndexed(), che fornisce l'indice. Per ulteriori dettagli, consulta il riferimento LazyListScope.

Griglie lente

I componibili LazyVerticalGrid e LazyHorizontalGrid supportano la visualizzazione degli elementi in una griglia. Una griglia verticale Lazy mostra gli elementi in un contenitore a scorrimento verticale, distribuiti su più colonne, mentre le griglie orizzontali Lazy avranno lo stesso comportamento sull'asse orizzontale.

Le griglie hanno le stesse potenti funzionalità API degli elenchi e utilizzano anche una connessione DSL molto simile LazyGridScope.() per descrivere i contenuti.

Screenshot di un telefono che mostra una griglia di foto

Il parametro columns in LazyVerticalGrid e il parametro rows in LazyHorizontalGrid controllano il modo in cui le celle vengono formattate in colonne o righe. Il seguente esempio mostra gli elementi in una griglia, utilizzando GridCells.Adaptive per impostare ogni colonna in modo che sia larga almeno 128.dp:

LazyVerticalGrid(
    columns = GridCells.Adaptive(minSize = 128.dp)
) {
    items(photos) { photo ->
        PhotoItem(photo)
    }
}

LazyVerticalGrid consente di specificare la larghezza degli elementi, dopodiché la griglia si adatterà il maggior numero possibile di colonne. L'eventuale larghezza rimanente viene distribuita equamente tra le colonne, dopo il calcolo del numero di colonne. Questo metodo adattivo delle dimensioni è particolarmente utile per mostrare insiemi di elementi su schermi di diverse dimensioni.

Se conosci il numero esatto di colonne da utilizzare, puoi fornire un'istanza di GridCells.Fixed contenente il numero di colonne obbligatorie.

Se il tuo design richiede che solo alcuni elementi abbiano dimensioni non standard, puoi utilizzare il supporto della griglia per fornire intervalli di colonna personalizzati per gli elementi. Specifica l'intervallo di colonne con il parametro span dei metodi LazyGridScope DSL item e items. maxLineSpan, uno dei valori dell'ambito di intervallo, è particolarmente utile quando utilizzi il dimensionamento adattiva, perché il numero di colonne non è fisso. Questo esempio mostra come fornire un intervallo di righe intera:

LazyVerticalGrid(
    columns = GridCells.Adaptive(minSize = 30.dp)
) {
    item(span = {
        // LazyGridItemSpanScope:
        // maxLineSpan
        GridItemSpan(maxLineSpan)
    }) {
        CategoryCard("Fruits")
    }
    // ...
}

Griglia scaglionata e scaglionata

LazyVerticalStaggeredGrid e LazyHorizontalStaggeredGrid sono componibili che consentono di creare una griglia di elementi scaglionata a caricamento lento. Una griglia sfalsata verticale pigra mostra gli elementi in un contenitore scorrevole verticale che si estende su più colonne e consente ai singoli elementi di avere altezze diverse. Le griglie orizzontali lente hanno lo stesso comportamento sull'asse orizzontale con elementi di larghezza diversa.

Lo snippet seguente è un esempio di base di come utilizzare LazyVerticalStaggeredGrid con una larghezza di 200.dp per elemento:

LazyVerticalStaggeredGrid(
    columns = StaggeredGridCells.Adaptive(200.dp),
    verticalItemSpacing = 4.dp,
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    content = {
        items(randomSizedPhotos) { photo ->
            AsyncImage(
                model = photo,
                contentScale = ContentScale.Crop,
                contentDescription = null,
                modifier = Modifier.fillMaxWidth().wrapContentHeight()
            )
        }
    },
    modifier = Modifier.fillMaxSize()
)

Figura 1. Esempio di griglia verticale sfalsata pigra

Per impostare un numero fisso di colonne, puoi utilizzare StaggeredGridCells.Fixed(columns) anziché StaggeredGridCells.Adaptive. Divide la larghezza disponibile per il numero di colonne (o righe per una griglia orizzontale) e ogni elemento occupa la larghezza (o l'altezza di una griglia orizzontale):

LazyVerticalStaggeredGrid(
    columns = StaggeredGridCells.Fixed(3),
    verticalItemSpacing = 4.dp,
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    content = {
        items(randomSizedPhotos) { photo ->
            AsyncImage(
                model = photo,
                contentScale = ContentScale.Crop,
                contentDescription = null,
                modifier = Modifier.fillMaxWidth().wrapContentHeight()
            )
        }
    },
    modifier = Modifier.fillMaxSize()
)

Griglia di immagini sfalsate e sfalsate in Compose
Figura 2. Esempio di griglia verticale sfalsata e sfalsata con colonne fisse

Spaziatura interna dei contenuti

A volte è necessario aggiungere una spaziatura interna intorno ai bordi dei contenuti. I componenti lenti ti consentono di passare parte di PaddingValues al parametro contentPadding al fine di supportare questa operazione:

LazyColumn(
    contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
) {
    // ...
}

In questo esempio, aggiungiamo 16.dp di spaziatura interna ai bordi orizzontali (sinistra e destra), quindi 8.dp nella parte superiore e inferiore dei contenuti.

Tieni presente che questa spaziatura interna viene applicata ai contenuti, non all'LazyColumn stesso. Nell'esempio precedente, il primo elemento aggiungerà la spaziatura interna 8.dp alla parte superiore, l'ultimo aggiungerà 8.dp alla parte inferiore e tutti gli elementi avranno una spaziatura interna 16.dp a sinistra e a destra.

Spaziatura dei contenuti

Per aggiungere spazio tra gli elementi, puoi utilizzare Arrangement.spacedBy(). L'esempio seguente aggiunge 4.dp di spazio tra un elemento e l'altro:

LazyColumn(
    verticalArrangement = Arrangement.spacedBy(4.dp),
) {
    // ...
}

Analogamente a LazyRow:

LazyRow(
    horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
    // ...
}

Le griglie, tuttavia, accettano sia la disposizione verticale che quella orizzontale:

LazyVerticalGrid(
    columns = GridCells.Fixed(2),
    verticalArrangement = Arrangement.spacedBy(16.dp),
    horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
    items(photos) { item ->
        PhotoItem(item)
    }
}

Chiavi degli elementi

Per impostazione predefinita, lo stato di ogni elemento è basato sulla posizione dell'elemento nell'elenco o nella griglia. Tuttavia, ciò può causare problemi se il set di dati cambia, poiché gli elementi che cambiano posizione perdono effettivamente qualsiasi stato memorizzato. Se immagini lo scenario di LazyRow all'interno di un elemento LazyColumn, se la riga cambia la posizione dell'elemento, l'utente perderà la posizione di scorrimento all'interno della riga.

Per ovviare a questo problema, puoi fornire una chiave stabile e univoca per ogni elemento, fornendo un blocco al parametro key. Fornire una chiave stabile consente allo stato degli elementi di essere coerente tra le modifiche al set di dati:

LazyColumn {
    items(
        items = messages,
        key = { message ->
            // Return a stable + unique key for the item
            message.id
        }
    ) { message ->
        MessageRow(message)
    }
}

Fornendo i tasti, aiuti Compose a gestire correttamente i riordinamenti. Ad esempio, se l'elemento contiene uno stato memorizzato, l'impostazione delle chiavi consente a Compose di spostare questo stato insieme all'elemento, quando la sua posizione cambia.

LazyColumn {
    items(books, key = { it.id }) {
        val rememberedValue = remember {
            Random.nextInt()
        }
    }
}

Tuttavia, esiste un limite ai tipi che puoi utilizzare come chiavi degli elementi. Il tipo della chiave deve essere supportato da Bundle, il meccanismo di Android che consente di mantenere gli stati quando l'attività viene ricreata. Bundle supporta tipi come primitive, enum o particolabili.

LazyColumn {
    items(books, key = {
        // primitives, enums, Parcelable, etc.
    }) {
        // ...
    }
}

La chiave deve essere supportata da Bundle affinché l'elemento rememberSaveable all'interno dell'elemento componibile possa essere ripristinato quando l'attività viene ricreata o anche quando scorri fuori dall'elemento e scorri indietro.

LazyColumn {
    items(books, key = { it.id }) {
        val rememberedValue = rememberSaveable {
            Random.nextInt()
        }
    }
}

Animazioni elemento

Se hai utilizzato il widget RecyclerView, saprai che anima le modifiche degli elementi automaticamente. I layout lenti offrono la stessa funzionalità per il riordinamento degli elementi. L'API è semplice: devi solo impostare il modificatore di animateItemPlacement sui contenuti dell'elemento:

LazyColumn {
    items(books, key = { it.id }) {
        Row(Modifier.animateItemPlacement()) {
            // ...
        }
    }
}

Puoi anche fornire specifiche per l'animazione personalizzata, se hai bisogno di:

LazyColumn {
    items(books, key = { it.id }) {
        Row(
            Modifier.animateItemPlacement(
                tween(durationMillis = 250)
            )
        ) {
            // ...
        }
    }
}

Assicurati di fornire le chiavi per i tuoi elementi in modo che sia possibile trovare la nuova posizione dell'elemento spostato.

Oltre ai riordinamenti, anche le animazioni degli elementi per aggiunte e rimozioni sono in fase di sviluppo. Puoi monitorare l'avanzamento nel problema 150812265.

Intestazioni permanenti (sperimentale)

Il pattern "intestazione persistente" è utile quando vengono visualizzati elenchi di dati raggruppati. Di seguito puoi vedere un esempio di "elenco contatti", raggruppato in base all'iniziale di ciascun contatto:

Video di uno smartphone che scorre verso l&#39;alto e verso il basso un elenco di contatti

Per ottenere un'intestazione fissa con LazyColumn, puoi utilizzare la funzione sperimentale stickyHeader() che fornisce i contenuti dell'intestazione:

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ListWithHeader(items: List<Item>) {
    LazyColumn {
        stickyHeader {
            Header()
        }

        items(items) { item ->
            ItemRow(item)
        }
    }
}

Per ottenere un elenco con più intestazioni, come nell'esempio "elenco contatti" riportato sopra, puoi:

// This ideally would be done in the ViewModel
val grouped = contacts.groupBy { it.firstName[0] }

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ContactsList(grouped: Map<Char, List<Contact>>) {
    LazyColumn {
        grouped.forEach { (initial, contactsForInitial) ->
            stickyHeader {
                CharacterHeader(initial)
            }

            items(contactsForInitial) { contact ->
                ContactListItem(contact)
            }
        }
    }
}

Reazione alla posizione di scorrimento

Molte app devono reagire e ascoltare le modifiche alla posizione di scorrimento e al layout degli elementi. I componenti Lazy supportano questo caso d'uso sollevando LazyListState:

@Composable
fun MessageList(messages: List<Message>) {
    // Remember our own LazyListState
    val listState = rememberLazyListState()

    // Provide it to LazyColumn
    LazyColumn(state = listState) {
        // ...
    }
}

Per casi d'uso semplici, le app di solito hanno bisogno di conoscere solo le informazioni relative al primo elemento visibile. Per questo motivo, LazyListState fornisce le proprietà firstVisibleItemIndex e firstVisibleItemScrollOffset.

Se usiamo l'esempio di come mostrare e nascondere un pulsante in base al fatto che l'utente abbia fatto scorrere la pagina oltre il primo elemento:

@OptIn(ExperimentalAnimationApi::class)
@Composable
fun MessageList(messages: List<Message>) {
    Box {
        val listState = rememberLazyListState()

        LazyColumn(state = listState) {
            // ...
        }

        // Show the button if the first visible item is past
        // the first item. We use a remembered derived state to
        // minimize unnecessary compositions
        val showButton by remember {
            derivedStateOf {
                listState.firstVisibleItemIndex > 0
            }
        }

        AnimatedVisibility(visible = showButton) {
            ScrollToTopButton()
        }
    }
}

Leggere lo stato direttamente nella composizione è utile quando devi aggiornare altri elementi componibili dell'interfaccia utente, ma in alcuni casi non è necessario gestire l'evento nella stessa composizione. Un esempio comune è l'invio di un evento di analisi dopo che l'utente ha fatto scorrere la pagina oltre un determinato punto. Per gestire efficacemente il problema, possiamo utilizzare un elemento snapshotFlow():

val listState = rememberLazyListState()

LazyColumn(state = listState) {
    // ...
}

LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .map { index -> index > 0 }
        .distinctUntilChanged()
        .filter { it }
        .collect {
            MyAnalyticsService.sendScrolledPastFirstItemEvent()
        }
}

LazyListState fornisce anche informazioni su tutti gli elementi attualmente visualizzati e sui relativi limiti sullo schermo, tramite la proprietà layoutInfo. Per ulteriori informazioni, consulta la lezione LazyListLayoutInfo.

Controllo della posizione di scorrimento

Oltre a reagire alla posizione di scorrimento, è utile anche che le app siano in grado di controllarla. LazyListState supporta questa operazione tramite la funzione scrollToItem(), che aggancia "immediatamente" la posizione di scorrimento e animateScrollToItem() che scorre con un'animazione (detta anche scorrimento fluido):

@Composable
fun MessageList(messages: List<Message>) {
    val listState = rememberLazyListState()
    // Remember a CoroutineScope to be able to launch
    val coroutineScope = rememberCoroutineScope()

    LazyColumn(state = listState) {
        // ...
    }

    ScrollToTopButton(
        onClick = {
            coroutineScope.launch {
                // Animate scroll to the first item
                listState.animateScrollToItem(index = 0)
            }
        }
    )
}

Set di dati di grandi dimensioni (paging)

La libreria di Paging consente alle app di supportare lunghi elenchi di elementi, caricando e visualizzando piccoli blocchi dell'elenco se necessario. Le pagine 3.0 e successive forniscono il supporto di Compose tramite la libreria androidx.paging:paging-compose.

Per visualizzare un elenco di contenuti delle pagine, possiamo utilizzare la funzione di estensione collectAsLazyPagingItems() e poi passare l'elemento LazyPagingItems restituito a items() nel nostro LazyColumn. Analogamente al supporto delle pagine cercate nelle viste, puoi mostrare dei segnaposto durante il caricamento dei dati controllando se item è null:

@Composable
fun MessageList(pager: Pager<Int, Message>) {
    val lazyPagingItems = pager.flow.collectAsLazyPagingItems()

    LazyColumn {
        items(
            lazyPagingItems.itemCount,
            key = lazyPagingItems.itemKey { it.id }
        ) { index ->
            val message = lazyPagingItems[index]
            if (message != null) {
                MessageRow(message)
            } else {
                MessagePlaceholder()
            }
        }
    }
}

Suggerimenti sull'uso dei layout lenti

Esistono alcuni suggerimenti che puoi prendere in considerazione per assicurarti che i layout Lazy funzionino come previsto.

Evita di utilizzare elementi con dimensioni pari a 0 pixel.

Questo può accadere in scenari in cui, ad esempio, prevedi di recuperare in modo asincrono alcuni dati come le immagini per riempire gli elementi dell'elenco in una fase successiva. Di conseguenza, il layout Lazy comporrà tutti gli elementi nella prima misurazione, poiché la loro altezza è di 0 pixel e potrebbe inserirli tutti nell'area visibile. Dopo che gli elementi sono stati caricati e la loro altezza espansa, i layout Lazy ignorano tutti gli altri elementi che sono stati inutilmente composti la prima volta, poiché non possono effettivamente rientrare nell'area visibile. Per evitare che ciò accada, devi impostare le dimensioni predefinite per i tuoi elementi, in modo che il layout Lazy possa fare il calcolo corretto del numero di elementi che possono effettivamente rientrare nell'area visibile:

@Composable
fun Item(imageUrl: String) {
    AsyncImage(
        model = rememberAsyncImagePainter(model = imageUrl),
        modifier = Modifier.size(30.dp),
        contentDescription = null
        // ...
    )
}

Quando conosci le dimensioni approssimative dei tuoi articoli dopo il caricamento asincrono dei dati, è buona norma assicurarsi che le dimensioni dei tuoi articoli rimangano le stesse prima e dopo il caricamento, ad esempio aggiungendo alcuni segnaposto. Ciò consentirà di mantenere la posizione di scorrimento corretta.

Evita di nidificare i componenti scorrevoli nella stessa direzione

Questo vale solo per i casi in cui si nidificano gli elementi secondari scorrevoli senza una dimensione predefinita all'interno di un'altra dimensione principale scorrevole nella stessa direzione. Ad esempio, se cerchi di nidificare un elemento secondario LazyColumn senza un'altezza fissa all'interno di un elemento principale Column scorrevole:

// throws IllegalStateException
Column(
    modifier = Modifier.verticalScroll(state)
) {
    LazyColumn {
        // ...
    }
}

Puoi ottenere lo stesso risultato includendo tutti i componenti componibili in un unico elemento principale LazyColumn e utilizzando la rispettiva DSL per trasferire tipi diversi di contenuti. Ciò consente l'emissione di singoli elementi, oltre a più voci di elenco, tutti in un unico posto:

LazyColumn {
    item {
        Header()
    }
    items(data) { item ->
        PhotoItem(item)
    }
    item {
        Footer()
    }
}

Tieni presente che sono consentiti i casi in cui nidi i layout di direzione diversi, ad esempio un elemento principale scorrevole Row e un elemento secondario LazyColumn:

Row(
    modifier = Modifier.horizontalScroll(scrollState)
) {
    LazyColumn {
        // ...
    }
}

nonché i casi in cui utilizzi comunque gli stessi layout di direzione, ma imposti anche una dimensione fissa per gli elementi secondari nidificati:

Column(
    modifier = Modifier.verticalScroll(scrollState)
) {
    LazyColumn(
        modifier = Modifier.height(200.dp)
    ) {
        // ...
    }
}

Non inserire più elementi in un unico elemento

In questo esempio, il secondo elemento lambda emette 2 elementi in un blocco:

LazyVerticalGrid(
    columns = GridCells.Adaptive(100.dp)
) {
    item { Item(0) }
    item {
        Item(1)
        Item(2)
    }
    item { Item(3) }
    // ...
}

I layout lenti gestiscono questo aspetto come previsto e dispongono gli elementi uno dopo l'altro come se fossero elementi diversi. Tuttavia, ciò presenta un paio di problemi.

Quando più elementi vengono emessi come parte di un singolo elemento, vengono gestiti come un'unica entità, il che significa che non possono più essere composti singolarmente. Se un elemento diventa visibile sullo schermo, tutti gli elementi corrispondenti all'elemento devono essere composti e misurati. Se utilizzata in modo eccessivo, potrebbe influire negativamente sulle prestazioni. Nel caso estremo di inserire tutti gli elementi in un unico elemento, questo supera completamente lo scopo di utilizzare i layout Lazy. Oltre a potenziali problemi di rendimento, l'inserimento di più elementi in un elemento interferirà anche con scrollToItem() e animateScrollToItem().

Tuttavia, esistono casi d'uso validi per inserire più elementi in un elemento, ad esempio avere divisori all'interno di un elenco. Non vuoi che i divisori cambino gli indici a scorrimento, poiché non devono essere considerati elementi indipendenti. Inoltre, le prestazioni non subiranno variazioni perché i divisori sono di piccole dimensioni. Un divisore dovrà essere visibile quando l'elemento prima di essere visibile, quindi può far parte dell'elemento precedente:

LazyVerticalGrid(
    columns = GridCells.Adaptive(100.dp)
) {
    item { Item(0) }
    item {
        Item(1)
        Divider()
    }
    item { Item(2) }
    // ...
}

Valuta la possibilità di utilizzare modalità personalizzate

In genere gli elenchi inattivi contengono molti elementi, che occupano più delle dimensioni del contenitore a scorrimento. Tuttavia, quando l'elenco viene compilato con pochi elementi, il design può avere requisiti più specifici per il posizionamento nell'area visibile.

Per ottenere questo risultato, puoi utilizzare il verticale personalizzato Arrangement e passarlo a LazyColumn. Nell'esempio seguente, l'oggetto TopWithFooter deve implementare solo il metodo arrange. Innanzitutto, posiziona gli elementi uno dopo l'altro. In secondo luogo, se l'altezza totale utilizzata è inferiore all'altezza dell'area visibile, il piè di pagina verrà posizionato in basso:

object TopWithFooter : Arrangement.Vertical {
    override fun Density.arrange(
        totalSize: Int,
        sizes: IntArray,
        outPositions: IntArray
    ) {
        var y = 0
        sizes.forEachIndexed { index, size ->
            outPositions[index] = y
            y += size
        }
        if (y < totalSize) {
            val lastIndex = outPositions.lastIndex
            outPositions[lastIndex] = totalSize - sizes.last()
        }
    }
}

Valuta la possibilità di aggiungere contentType

A partire da Compose 1.2, per massimizzare le prestazioni del layout Lazy, ti consigliamo di aggiungere contentType ai tuoi elenchi o alle griglie. In questo modo puoi specificare il tipo di contenuti per ogni elemento del layout, nei casi in cui stai componendo un elenco o una griglia composta da più tipi di elementi diversi:

LazyColumn {
    items(elements, contentType = { it.type }) {
        // ...
    }
}

Quando fornisci contentType, Compose può riutilizzare le composizioni solo tra gli elementi dello stesso tipo. Poiché il riutilizzo è più efficiente quando componi elementi con struttura simile, fornire i tipi di contenuti garantisce che Compose non provi a comporre un elemento di tipo A sopra un elemento completamente diverso di tipo B. Questo consente di massimizzare i vantaggi del riutilizzo della composizione e delle prestazioni del layout Lazy.

Misurazione del rendimento

Puoi misurare in modo affidabile le prestazioni di un layout Lazy solo quando è in esecuzione in modalità di rilascio e con l'ottimizzazione R8 abilitata. Sulle build di debug, lo scorrimento del layout lento potrebbe sembrare più lento. Per maggiori informazioni su questo argomento, consulta Prestazioni di Compose.