Ablauflayouts in Compose

FlowRow und FlowColumn sind zusammensetzbare Funktionen, die Row und Column ähneln, sich jedoch dadurch unterscheiden, dass Elemente in die nächste Zeile übergehen, wenn der Speicherplatz des Containers ausgeht. Dadurch werden mehrere Zeilen oder Spalten erstellt. Die Anzahl der Elemente in einer Zeile kann auch durch Festlegen von maxItemsInEachRow oder maxItemsInEachColumn gesteuert werden. Häufig können Sie FlowRow und FlowColumn zum Erstellen responsiver Layouts verwenden. Inhalte werden nicht abgeschnitten, wenn Elemente zu groß für eine Dimension sind. Wenn Sie eine Kombination aus maxItemsInEach* und Modifier.weight(weight) verwenden, können Layouts erstellt werden, bei denen die Breite einer Zeile oder Spalte bei Bedarf ausgefüllt oder erweitert wird.

Das typische Beispiel ist für einen Chip oder eine Filter-UI:

5 Chips in einer FlowRow, die den Überlauf zur nächsten Zeile anzeigen, wenn kein Platz mehr verfügbar ist.
Abbildung 1: Beispiel für FlowRow

Grundlegende Verwendung

Um FlowRow oder FlowColumn zu verwenden, erstellen Sie diese zusammensetzbaren Funktionen und platzieren Sie die Elemente darin. Diese sollten dem Standardablauf folgen:

@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")
    }
}

Durch dieses Snippet wird die oben gezeigte Benutzeroberfläche angezeigt, in der Elemente automatisch in die nächste Zeile fließen, wenn in der ersten Zeile kein Platz mehr vorhanden ist.

Features des Flusslayouts

Ablauflayouts haben die folgenden Funktionen und Eigenschaften, mit denen Sie verschiedene Layouts in Ihrer Anwendung erstellen können.

Anordnung der Hauptachse: horizontal oder vertikal angeordnet

Die Hauptachse ist die Achse, auf der Elemente angeordnet sind. In FlowRow sind Elemente beispielsweise horizontal angeordnet. Mit dem Parameter horizontalArrangement in FlowRow wird gesteuert, wie der kostenlose Speicherplatz auf die Elemente aufgeteilt wird.

Die folgende Tabelle zeigt Beispiele für das Festlegen von horizontalArrangement für Elemente für FlowRow:

Horizontale Anordnung für FlowRow festgelegt

Ergebnis

Arrangement.Start (Default)

Artikel am Anfang angeordnet

Arrangement.SpaceBetween

Anordnung von Elementen mit Abstand dazwischen

Arrangement.Center

In der Mitte angeordnete Artikel

Arrangement.End

Artikel am Ende angeordnet

Arrangement.SpaceAround

Gegenstände, die um sie herum angeordnet sind

Arrangement.spacedBy(8.dp)

Elemente mit einem bestimmten dp Abstand

Für FlowColumn sind ähnliche Optionen mit verticalArrangement verfügbar. Der Standardwert ist Arrangement.Top.

Kreuzachsenanordnung

Die Kreuzachse ist die Achse, die der Hauptachse entgegengesetzt ist. In FlowRow ist dies beispielsweise die vertikale Achse. Wenn Sie ändern möchten, wie der Gesamtinhalt innerhalb des Containers in der Kreuzachse angeordnet wird, verwenden Sie verticalArrangement für FlowRow und horizontalArrangement für FlowColumn.

Die folgende Tabelle zeigt Beispiele für das Festlegen verschiedener verticalArrangement für die Elemente für FlowRow:

Vertikale Anordnung auf FlowRow festgelegt

Ergebnis

Arrangement.Top (Default)

Anordnung der Container oben

Arrangement.Bottom

Anordnung der Container unten

Arrangement.Center

Container Center-Anordnung

Für FlowColumn sind ähnliche Optionen mit horizontalArrangement verfügbar. Die Standardanordnung der Querachsen ist Arrangement.Start.

Ausrichtung einzelner Elemente

Sie können einzelne Elemente innerhalb der Zeile mit unterschiedlichen Ausrichtungen positionieren. Dieser Unterschied unterscheidet sich von verticalArrangement und horizontalArrangement, da hier die Elemente innerhalb der aktuellen Zeile ausgerichtet werden. Sie können dies mit Modifier.align() anwenden.

Wenn beispielsweise Elemente in einem FlowRow unterschiedliche Höhen haben, wird in der Zeile die Höhe des größten Elements verwendet und Modifier.align(alignmentOption) wird auf die Elemente angewendet:

Vertikale Ausrichtung auf FlowRow festgelegt

Ergebnis

Alignment.Top (Default)

Oben ausgerichtete Elemente

Alignment.Bottom

Elemente unten ausrichten

Alignment.CenterVertically

Elemente zentriert

Für FlowColumn sind ähnliche Optionen verfügbar. Die Standardausrichtung ist Alignment.Start.

Maximale Anzahl von Elementen in Zeile oder Spalte

Die Parameter maxItemsInEachRow oder maxItemsInEachColumn definieren die maximale Anzahl von Elementen auf der Hauptachse, die in einer Zeile zulässig sind, bevor in die nächste Zeile umgebrochen wird. Die Standardeinstellung ist Int.MAX_INT. Damit sind so viele Elemente wie möglich zulässig, solange sie in die Zeile passen.

Wenn Sie beispielsweise maxItemsInEachRow festlegen, hat das anfängliche Layout nur drei Elemente:

Kein Höchstwert festgelegt

maxItemsInEachRow = 3

Kein Maximalwert für Flusszeile festgelegt Maximale Anzahl von Elementen in der Ablaufzeile festgelegt

Lazy Loading von Elementen

ContextualFlowRow und ContextualFlowColumn sind eine spezielle Version von FlowRow und FlowColumn, mit der Sie den Inhalt einer Zeile oder Spalte eines Ablaufs per Lazy Loading laden können. Sie liefern auch Informationen zur Position des Elements (Index, Zeilennummer und verfügbare Größe), z. B. ob sich das Element in der ersten Zeile befindet. Dies ist nützlich bei großen Datasets und wenn Sie Kontextinformationen zu einem Element benötigen.

Der Parameter maxLines begrenzt die Anzahl der angezeigten Zeilen. Der Parameter overflow gibt an, was angezeigt werden soll, wenn ein Überlauf von Elementen erreicht wird. So können Sie einen benutzerdefinierten expandIndicator- oder collapseIndicator-Wert angeben.

So können Sie beispielsweise die Schaltfläche „+ (Anzahl der verbleibenden Elemente)“ oder „Weniger anzeigen“ anzeigen lassen:

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")
}

Beispiel für kontextbezogene Ablaufzeilen.
Abbildung 2: Beispiel für ContextualFlowRow

Artikelgewichte

Mit der Gewichtung vergrößern Sie ein Element basierend auf seinem Faktor und dem verfügbaren Platz auf der Linie, in der es platziert wurde. Wichtig: Bei der Berechnung der Breite eines Artikels anhand der Gewichtung gibt es einen Unterschied zwischen FlowRow und Row. Bei Rows basiert die Gewichtung auf allen Artikeln in Row. Bei FlowRow basiert die Gewichtung auf den Elementen in der Position, in der ein Element platziert wird, und nicht auf allen Elementen im Container FlowRow.

Wenn Sie beispielsweise 4 Elemente haben, die alle auf eine Linie fallen und jedes Element eine andere Gewichtung von 1f, 2f, 1f und 3f hat, beträgt die Gesamtgewichtung 7f. Der verbleibende Bereich in einer Zeile oder Spalte wird durch 7f geteilt. Anschließend wird die Breite jedes Elements anhand von weight * (remainingSpace / totalWeight) berechnet.

Sie können Modifier.weight und max. Elemente mit FlowRow oder FlowColumn kombinieren, um ein rasterähnliches Layout zu erstellen. Dieser Ansatz ist nützlich, um responsive Layouts zu erstellen, die sich an die Größe des Geräts anpassen.

Es gibt verschiedene Beispiele dafür, was Sie mit Gewichtungen erreichen können. Ein Beispiel ist ein Raster, in dem Elemente die gleiche Größe haben, wie unten gezeigt:

Raster mit Flusszeile erstellt
Abbildung 3: Raster mit FlowRow erstellen

So erstellen Sie ein Raster gleicher Elementgrößen:

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)
    }
}

Wenn Sie ein weiteres Element hinzufügen und es zehn statt neunmal wiederholen, nimmt das letzte Element die gesamte letzte Spalte ein, da die Gesamtgewichtung für die gesamte Zeile 1f beträgt:

Letztes Element in voller Größe im Raster
Abbildung 4: Mit FlowRow ein Raster erstellen, wobei das letzte Element die gesamte Breite einnimmt

Sie können Gewichtungen mit anderen Modifiers wie Modifier.width(exactDpAmount), Modifier.aspectRatio(aspectRatio) oder Modifier.fillMaxWidth(fraction) kombinieren. Alle diese Modifikatoren sorgen dafür, dass die Größe von Elementen innerhalb von FlowRow (oder FlowColumn) responsiv angepasst werden kann.

Sie können auch ein abwechselndes Raster mit unterschiedlichen Elementgrößen erstellen, wobei zwei Elemente jeweils die halbe Breite einnehmen und ein Element die volle Breite der nächsten Spalte einnimmt:

Abwechselndes Raster mit Flusszeile
Abbildung 5: FlowRow mit abwechselnden Zeilengrößen

Dazu können Sie folgenden Code verwenden:

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))
        }
    }
}

Fraktionale Größen

Mit Modifier.fillMaxWidth(fraction) können Sie die Größe des Containers angeben, den ein Element einnehmen soll. Dies unterscheidet sich von der Funktionsweise von Modifier.fillMaxWidth(fraction) bei Anwendung auf Row oder Column, da Row/Column-Elemente einen Prozentsatz der verbleibenden Breite und nicht die Breite des gesamten Containers einnehmen.

Der folgende Code führt beispielsweise zu unterschiedlichen Ergebnissen bei Verwendung von FlowRow und 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: Mittleres Element mit 0,7 Anteil an der gesamten Containerbreite.

Bruchbreite mit Flusszeile

Row: Mittleres Element, das 0,7 % der verbleibenden Breite von Row einnimmt.

Bruchbreite mit Zeile

fillMaxColumnWidth() und fillMaxRowHeight()

Durch die Anwendung von Modifier.fillMaxColumnWidth() oder Modifier.fillMaxRowHeight() auf ein Element in einem FlowColumn oder FlowRow wird sichergestellt, dass Elemente in derselben Spalte oder Zeile dieselbe Breite oder Höhe wie das größte Element in der Spalte/Zeile einnehmen.

In diesem Beispiel wird FlowColumn verwendet, um die Liste der Android-Desserts anzuzeigen. Sie sehen den Unterschied in der Breite der einzelnen Elemente, wenn Modifier.fillMaxColumnWidth() auf die Elemente angewendet wird und nicht und wenn die Elemente umgebrochen werden.

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() auf jedes Element angewendet

„fillMaxColumnWidth“

Keine Breitenänderungen festgelegt (Elemente werden umgebrochen)

Keine Spaltenbreite für die maximale Füllung festgelegt