Layout di flusso in Scrivi

FlowRow e FlowColumn sono componenti simili a Row e Column, ma differiscono in quanto gli elementi passano alla riga successiva quando lo spazio del contenitore finisce. Vengono create più righe o colonne. Il numero di elementi in una riga può essere controllato anche impostando maxItemsInEachRow o maxItemsInEachColumn. Spesso puoi utilizzare FlowRow e FlowColumn per creare layout adattabili: i contenuti non verranno tagliati se gli elementi sono troppo grandi per una dimensione e l'utilizzo di una combinazione di maxItemsInEach* con Modifier.weight(weight) può aiutarti a creare layout che completano/espandeno la larghezza di una riga o di una colonna, se necessario.

Un esempio tipico è un chip o un'interfaccia utente di filtro:

5 chip in una riga di flusso che mostrano lo sbordamento nella riga successiva quando non è più disponibile spazio.
Figura 1. Esempio di FlowRow

Utilizzo di base

Per utilizzare FlowRow o FlowColumn, crea questi composabili e inserisci al loro interno gli elementi che devono seguire il flusso standard:

@Composable
private fun FlowRowSimpleUsageExample() {
    FlowRow(modifier = Modifier.padding(8.dp)) {
        ChipItem("Price: High to Low")
        ChipItem("Avg rating: 4+")
        ChipItem("Free breakfast")
        ChipItem("Free cancellation")
        ChipItem("£50 pn")
    }
}

Questo snippet genera l'interfaccia utente mostrata sopra, con gli elementi che passano automaticamente alla riga successiva quando non è più presente spazio nella prima riga.

Funzionalità del layout del flusso

I layout di flusso hanno le seguenti funzionalità e proprietà che puoi utilizzare per creare layout diversi nella tua app.

Disposizione dell'asse principale: orizzontale o verticale

L'asse principale è l'asse su cui sono disposti gli elementi (ad esempio, in FlowRow gli elementi sono disposti orizzontalmente). Il parametro horizontalArrangement in FlowRow controlla il modo in cui lo spazio libero viene distribuito tra gli elementi.

La tabella seguente mostra esempi di impostazione di horizontalArrangement sugli elementi per FlowRow:

Disposizione orizzontale impostata su FlowRow

Risultato

Arrangement.Start (Default)

Elementi disposti in base all'inizio

Arrangement.SpaceBetween

Disposizione degli elementi con spazio intermedio

Arrangement.Center

Elementi disposti al centro

Arrangement.End

Elementi disposti alla fine

Arrangement.SpaceAround

Elementi disposti con spazio intorno

Arrangement.spacedBy(8.dp)

Elementi distanziati da un determinato numero di dp

Per FlowColumn, sono disponibili opzioni simili con verticalArrangement, con il valore predefinito Arrangement.Top.

Disposizione degli assi trasversali

L'asse trasversale è l'asse nella direzione opposta all'asse principale. Ad esempio, in FlowRow, si tratta dell'asse verticale. Per modificare la disposizione dei contenuti complessivi all'interno del contenitore nell'asse trasversale, utilizza verticalArrangement per FlowRow e horizontalArrangement per FlowColumn.

Per FlowRow, la tabella seguente mostra esempi di impostazione di diversi valori di verticalArrangement sugli elementi:

Disposizione verticale impostata su FlowRow

Risultato

Arrangement.Top (Default)

Disposizione della parte superiore del contenitore

Arrangement.Bottom

Disposizione del fondo del contenitore

Arrangement.Center

Posizionamento del contenitore al centro

Per FlowColumn, sono disponibili opzioni simili con horizontalArrangement. L'ordinamento dell'asse trasversale predefinito è Arrangement.Start.

Allineamento dei singoli elementi

Potresti voler posizionare i singoli elementi all'interno della riga con allineamenti diversi. È diverso da verticalArrangement e horizontalArrangement perché allinea gli elementi all'interno della riga corrente. Puoi applicarlo con Modifier.align().

Ad esempio, quando gli elementi di un FlowRow sono di altezze diverse, la riga prende l'altezza dell'elemento più grande e applica Modifier.align(alignmentOption) agli elementi:

Allineamento verticale impostato su FlowRow

Risultato

Alignment.Top (Default)

Elementi allineati in alto

Alignment.Bottom

Elementi allineati in basso

Alignment.CenterVertically

Elementi allineati al centro

Per FlowColumn sono disponibili opzioni simili. L'allineamento predefinito è Alignment.Start.

Numero massimo di elementi in una riga o una colonna

I parametri maxItemsInEachRow o maxItemsInEachColumn definiscono il numero massimo di elementi nell'asse principale consentiti in una riga prima di passare alla riga successiva. Il valore predefinito è Int.MAX_INT, che consente il maggior numero possibile di elementi, purché le dimensioni consentano di inserirli nella riga.

Ad esempio, l'impostazione di un maxItemsInEachRow forza il layout iniziale ad avere solo tre elementi:

Nessun limite massimo impostato

maxItemsInEachRow = 3

Nessun valore massimo impostato nella riga del flusso Elementi massimi impostati nella riga del flusso

Elementi del flusso con caricamento lento

ContextualFlowRow e ContextualFlowColumn sono una versione specializzata di FlowRow e FlowColumn che ti consente di eseguire il caricamento lazy dei contenuti della riga o della colonna del flusso. Forniscono inoltre informazioni sulla posizione degli articoli (indice, numero di riga e dimensioni disponibili), ad esempio se l'articolo si trova nella prima riga. Questo è utile per set di dati di grandi dimensioni e se hai bisogno di informazioni contestuali su un elemento.

Il parametro maxLines limita il numero di righe visualizzate e il parametro overflow specifica cosa deve essere visualizzato quando viene raggiunto un overflow di elementi, consentendoti di specificare un expandIndicator o un collapseIndicator personalizzato.

Ad esempio, per mostrare un pulsante "+ (numero di elementi rimanenti)" o "Mostra meno":

val totalCount = 40
var maxLines by remember {
    mutableStateOf(2)
}

val moreOrCollapseIndicator = @Composable { scope: ContextualFlowRowOverflowScope ->
    val remainingItems = totalCount - scope.shownItemCount
    ChipItem(if (remainingItems == 0) "Less" else "+$remainingItems", onClick = {
        if (remainingItems == 0) {
            maxLines = 2
        } else {
            maxLines += 5
        }
    })
}
ContextualFlowRow(
    modifier = Modifier
        .safeDrawingPadding()
        .fillMaxWidth(1f)
        .padding(16.dp)
        .wrapContentHeight(align = Alignment.Top)
        .verticalScroll(rememberScrollState()),
    verticalArrangement = Arrangement.spacedBy(4.dp),
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    maxLines = maxLines,
    overflow = ContextualFlowRowOverflow.expandOrCollapseIndicator(
        minRowsToShowCollapse = 4,
        expandIndicator = moreOrCollapseIndicator,
        collapseIndicator = moreOrCollapseIndicator
    ),
    itemCount = totalCount
) { index ->
    ChipItem("Item $index")
}

Esempio di righe di flusso contestuale.
Figura 2. Esempio di ContextualFlowRow

Pesi degli articoli

Il peso aumenta un elemento in base al suo fattore e allo spazio disponibile nella riga in cui è stato inserito. È importante sottolineare che esiste una differenza tra FlowRow e Row per quanto riguarda il modo in cui vengono utilizzati i pesi per calcolare la larghezza di un elemento. Per Rows, il peso si basa su tutti gli elementi in Row. Con FlowRow, il peso si basa sugli elementi della riga in cui è inserito un elemento, non su tutti gli elementi del contenutore FlowRow.

Ad esempio, se hai 4 articoli che rientrano tutti in una riga, ciascuno con ponderazioni diverse di 1f, 2f, 1f e 3f, la ponderazione totale è 7f. Lo spazio rimanente in una riga o una colonna verrà diviso per 7f. La larghezza di ogni elemento verrà calcolata utilizzando: weight * (remainingSpace / totalWeight).

Puoi utilizzare una combinazione di elementi Modifier.weight e max con FlowRow o FlowColumn per creare un layout simile a una griglia. Questo approccio è utile per creare layout adattabili che si regolano in base alle dimensioni del dispositivo.

Esistono diversi esempi di ciò che puoi ottenere utilizzando i pesi. Un esempio è una griglia in cui gli elementi hanno le stesse dimensioni, come mostrato di seguito:

Griglia creata con riga di flusso
Figura 3. Utilizzare FlowRow per creare una griglia

Per creare una griglia di elementi di dimensioni uguali, puoi procedere nel seguente modo:

val rows = 3
val columns = 3
FlowRow(
    modifier = Modifier.padding(4.dp),
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    maxItemsInEachRow = rows
) {
    val itemModifier = Modifier
        .padding(4.dp)
        .height(80.dp)
        .weight(1f)
        .clip(RoundedCornerShape(8.dp))
        .background(MaterialColors.Blue200)
    repeat(rows * columns) {
        Spacer(modifier = itemModifier)
    }
}

È importante sottolineare che, se aggiungi un altro elemento e lo ripeti 10 volte anziché 9, l'ultimo elemento occupa l'intera ultima colonna, poiché il peso totale per l'intera riga è 1f:

Ultimo elemento a grandezza naturale nella griglia
Figura 4. Utilizzo di FlowRow per creare una griglia con l'ultimo elemento che occupa l'intera larghezza

Puoi combinare i pesi con altri Modifiers, ad esempio Modifier.width(exactDpAmount), Modifier.aspectRatio(aspectRatio) o Modifier.fillMaxWidth(fraction). Questi modificatori agiscono tutti insieme per consentire il ridimensionamento adattabile degli elementi all'interno di un FlowRow (o FlowColumn).

Puoi anche creare una griglia alternata di elementi di dimensioni diverse, in cui due elementi occupano ciascuno metà della larghezza e un elemento occupa l'intera larghezza della colonna successiva:

Griglia alternata con riga di flusso
Figura 5. FlowRow con righe di dimensioni alternate

Puoi farlo con il seguente codice:

FlowRow(
    modifier = Modifier.padding(4.dp),
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    maxItemsInEachRow = 2
) {
    val itemModifier = Modifier
        .padding(4.dp)
        .height(80.dp)
        .clip(RoundedCornerShape(8.dp))
        .background(Color.Blue)
    repeat(6) { item ->
        // if the item is the third item, don't use weight modifier, but rather fillMaxWidth
        if ((item + 1) % 3 == 0) {
            Spacer(modifier = itemModifier.fillMaxWidth())
        } else {
            Spacer(modifier = itemModifier.weight(0.5f))
        }
    }
}

Dimensionamento frazionato

Utilizzando Modifier.fillMaxWidth(fraction), puoi specificare le dimensioni del contenitore che un articolo deve occupare. Questo è diverso dal funzionamento di Modifier.fillMaxWidth(fraction) quando viene applicato a Row o Column, in quanto gli elementi Row/Column occupano una percentuale della larghezza rimanente anziché la larghezza dell'intero contenitore.

Ad esempio, il seguente codice produce risultati diversi se utilizzi FlowRow o Row:

FlowRow(
    modifier = Modifier.padding(4.dp),
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    maxItemsInEachRow = 3
) {
    val itemModifier = Modifier
        .clip(RoundedCornerShape(8.dp))
    Box(
        modifier = itemModifier
            .height(200.dp)
            .width(60.dp)
            .background(Color.Red)
    )
    Box(
        modifier = itemModifier
            .height(200.dp)
            .fillMaxWidth(0.7f)
            .background(Color.Blue)
    )
    Box(
        modifier = itemModifier
            .height(200.dp)
            .weight(1f)
            .background(Color.Magenta)
    )
}

FlowRow: elemento centrale con una frazione di 0,7 della larghezza dell'intero contenitore.

Larghezza frazionaria con riga di flusso

Row: elemento centrale che occupa lo 0,7% della larghezza rimanente di Row.

Larghezza frazionaria con riga

fillMaxColumnWidth() e fillMaxRowHeight()

L'applicazione di Modifier.fillMaxColumnWidth() o Modifier.fillMaxRowHeight() a un elemento all'interno di un FlowColumn o FlowRow assicura che gli elementi nella stessa colonna o riga abbiano la stessa larghezza o altezza dell'elemento più grande della colonna/riga.

Ad esempio, questo esempio utilizza FlowColumn per visualizzare l'elenco dei dessert Android. Puoi vedere la differenza nelle larghezze di ogni elemento quando viene applicato Modifier.fillMaxColumnWidth() rispetto a quando non viene applicato e gli elementi vengono a capo.

FlowColumn(
    Modifier
        .padding(20.dp)
        .fillMaxHeight()
        .fillMaxWidth(),
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    verticalArrangement = Arrangement.spacedBy(8.dp),
    maxItemsInEachColumn = 5,
) {
    repeat(listDesserts.size) {
        Box(
            Modifier
                .fillMaxColumnWidth()
                .border(1.dp, Color.DarkGray, RoundedCornerShape(8.dp))
                .padding(8.dp)
        ) {

            Text(
                text = listDesserts[it],
                fontSize = 18.sp,
                modifier = Modifier.padding(3.dp)
            )
        }
    }
}

Modifier.fillMaxColumnWidth() applicata a ogni elemento

fillMaxColumnWidth

Nessuna modifica della larghezza impostata (aggiunzione di a capo agli elementi)

Nessuna larghezza massima della colonna di riempimento impostata