Mises en page de flux dans Compose

FlowRow et FlowColumn sont des composables semblables à Row et Column, mais qui se distinguent par le fait que les éléments sont insérés dans la ligne suivante lorsque le conteneur manque d'espace. Cela crée plusieurs lignes ou colonnes. Le nombre d'éléments dans une ligne peut également être contrôlé en définissant maxItemsInEachRow ou maxItemsInEachColumn. Vous pouvez souvent utiliser FlowRow et FlowColumn pour créer des mises en page responsives. Le contenu ne sera pas tronqué si les éléments sont trop grands pour une dimension, et l'utilisation d'une combinaison de maxItemsInEach* avec Modifier.weight(weight) peut vous aider à créer des mises en page qui remplissent/étendent la largeur d'une ligne ou d'une colonne si nécessaire.

L'exemple type est celui d'un chip ou d'une UI de filtrage:

Cinq chips dans une ligne FlowRow affichant le débordement vers la ligne suivante lorsqu'il n'y a plus d'espace disponible.
Figure 1. Exemple de FlowRow

Utilisation de base

Pour utiliser FlowRow ou FlowColumn, créez ces composables et placez-y les éléments qui doivent suivre le flux 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")
    }
}

Cet extrait de code affiche l'interface utilisateur ci-dessus, les éléments sont automatiquement transférés vers la ligne suivante lorsqu'il n'y a plus d'espace dans la première ligne.

Caractéristiques de la mise en page de flux

Les mises en page de flux disposent des fonctionnalités et propriétés suivantes, que vous pouvez utiliser pour créer différentes mises en page dans votre application.

Disposition de l'axe principal: disposition horizontale ou verticale

L'axe principal est celui sur lequel les éléments sont disposés (par exemple, dans FlowRow, les éléments sont disposés horizontalement). Le paramètre horizontalArrangement dans FlowRow contrôle la façon dont l'espace libre est réparti entre les éléments.

Le tableau suivant présente des exemples de définition de horizontalArrangement pour les éléments de FlowRow:

Configuration horizontale définie sur FlowRow

Résultat

Arrangement.Start (Default)

Éléments classés par date de début

Arrangement.SpaceBetween

Organisation des éléments avec un espace entre les deux

Arrangement.Center

Éléments disposés au centre

Arrangement.End

Éléments classés à la fin

Arrangement.SpaceAround

Éléments disposés en espace autour d'eux

Arrangement.spacedBy(8.dp)

Éléments espacés d'un certain dp

Pour FlowColumn, des options similaires sont disponibles avec verticalArrangement, avec la valeur par défaut Arrangement.Top.

Disposition de l'axe transversal

L'axe transversal est l'axe dans la direction opposée à l'axe principal. Par exemple, dans FlowRow, il s'agit de l'axe vertical. Pour modifier la disposition du contenu global à l'intérieur du conteneur sur l'axe transversal, utilisez verticalArrangement pour FlowRow et horizontalArrangement pour FlowColumn.

Pour FlowRow, le tableau suivant montre des exemples de définition de différentes verticalArrangement sur les éléments:

Disposition verticale définie sur FlowRow

Résultat

Arrangement.Top (Default)

Organisation du conteneur en haut

Arrangement.Bottom

Organisation du bas du conteneur

Arrangement.Center

Organisation du centre du conteneur

Pour FlowColumn, des options similaires sont disponibles avec horizontalArrangement. La disposition par défaut de l'axe transversal est Arrangement.Start.

Alignement d'éléments individuels

Vous pouvez positionner des éléments individuels sur la ligne avec des alignements différents. Cette approche est différente de verticalArrangement et horizontalArrangement, car elle aligne les éléments dans la ligne actuelle. Vous pouvez appliquer cela avec Modifier.align().

Par exemple, lorsque les éléments d'une FlowRow ont des hauteurs différentes, la ligne prend la hauteur de l'élément le plus grand et applique Modifier.align(alignmentOption) aux éléments:

Alignement vertical défini sur FlowRow

Résultat

Alignment.Top (Default)

Éléments alignés en haut

Alignment.Bottom

Éléments alignés en bas

Alignment.CenterVertically

Éléments alignés au centre

Pour FlowColumn, des options similaires sont disponibles. L'alignement par défaut est Alignment.Start.

Nombre maximal d'éléments par ligne ou par colonne

Les paramètres maxItemsInEachRow ou maxItemsInEachColumn définissent le nombre maximal d'articles sur l'axe principal à autoriser sur une ligne avant de passer à la suivante. La valeur par défaut est Int.MAX_INT, qui autorise autant d'éléments que possible, à condition que leur taille leur permette de tenir dans la ligne.

Par exemple, la définition d'un maxItemsInEachRow oblige la mise en page initiale à ne comporter que trois éléments:

Aucune valeur maximale définie

maxItemsInEachRow = 3

Aucune valeur maximale définie sur la ligne de flux Nombre maximal d'éléments défini sur la ligne de flux

Éléments du flux de chargement différé

ContextualFlowRow et ContextualFlowColumn sont des versions spécialisées de FlowRow et FlowColumn qui vous permettent de procéder au chargement différé du contenu de votre ligne ou colonne de flux. Elles fournissent également des informations sur la position des éléments (index, numéro de ligne et taille disponible), par exemple si l'élément se trouve sur la première ligne. Cela est utile pour les ensembles de données volumineux et si vous avez besoin d'informations contextuelles sur un élément.

Le paramètre maxLines limite le nombre de lignes affichées, et le paramètre overflow spécifie ce qui doit être affiché en cas de dépassement d'éléments, ce qui vous permet de spécifier un expandIndicator ou un collapseIndicator personnalisé.

Par exemple, pour afficher un bouton "+ (nombre d'éléments restants)" ou "Afficher moins" :

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

Exemple de lignes de flux contextuels.
Figure 2 : Exemple de ContextualFlowRow

Poids des éléments

Le poids augmente un élément en fonction de son facteur et de l'espace disponible sur la ligne dans laquelle il a été placé. Il est important de noter qu'il existe une différence entre FlowRow et Row concernant la façon dont les pondérations sont utilisées pour calculer la largeur d'un élément. Pour Rows, le poids est basé sur tous les articles de Row. Avec FlowRow, la pondération est basée sur les articles de la ligne dans lesquels un article est placé, et non sur tous ceux du conteneur FlowRow.

Par exemple, si quatre articles tombent tous sur une ligne, chacun ayant des pondérations différentes de 1f, 2f, 1f et 3f, le poids total est de 7f. L'espace restant dans une ligne ou une colonne sera divisé par 7f. Ensuite, chaque largeur d'article sera calculée à l'aide de weight * (remainingSpace / totalWeight).

Vous pouvez utiliser une combinaison de Modifier.weight et d'éléments maximum avec FlowRow ou FlowColumn pour créer une mise en page de type grille. Cette approche est utile pour créer des mises en page responsives qui s'adaptent à la taille de votre appareil.

Il existe plusieurs exemples de ce que vous pouvez accomplir à l'aide des pondérations. Un exemple est une grille dans laquelle les éléments sont de taille égale, comme illustré ci-dessous:

Grille créée avec une ligne de flux
Figure 3. Utiliser FlowRow pour créer une grille

Pour créer une grille de tailles égales pour les éléments, procédez comme suit:

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

Il est important de noter que si vous ajoutez un autre élément et que vous le répétez 10 fois au lieu de 9, le dernier élément occupe toute la dernière colonne, car la pondération totale pour la ligne entière est 1f:

Dernier élément en taille réelle sur la grille
Figure 4. Utilisation de FlowRow pour créer une grille avec le dernier élément occupant toute la largeur

Vous pouvez combiner les pondérations avec d'autres Modifiers, tels que Modifier.width(exactDpAmount), Modifier.aspectRatio(aspectRatio) ou Modifier.fillMaxWidth(fraction). Ces modificateurs fonctionnent tous conjointement pour permettre le dimensionnement responsif des éléments dans une FlowRow (ou FlowColumn).

Vous pouvez également créer une grille alternée de différentes tailles d'éléments, dans laquelle deux éléments occupent chacun la moitié de la largeur, et un élément occupe toute la largeur de la colonne suivante:

Grille en alternance avec une ligne de flux
Figure 5 : FlowRow avec des tailles de lignes alternées

Pour ce faire, utilisez le code suivant:

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

Dimensionnement par fractions

À l'aide de Modifier.fillMaxWidth(fraction), vous pouvez spécifier la taille du conteneur qu'un élément doit occuper. Ce fonctionnement est différent de celui de Modifier.fillMaxWidth(fraction) lorsqu'il est appliqué à Row ou Column, dans la mesure où les éléments Row/Column occupent un pourcentage de la largeur restante, et non la largeur totale du conteneur.

Par exemple, le code suivant produit des résultats différents lorsque vous utilisez FlowRow et 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: élément du milieu avec 0,7 fraction de la largeur totale du conteneur.

Largeur fractionnaire avec ligne de flux

Row: élément du milieu qui occupe 0,7 % de la largeur Row restante.

Largeur fractionnaire avec ligne

fillMaxColumnWidth() et fillMaxRowHeight()

L'application de Modifier.fillMaxColumnWidth() ou Modifier.fillMaxRowHeight() à un élément d'une FlowColumn ou d'une FlowRow garantit que les éléments de la même colonne ou ligne occupent la même largeur ou la même hauteur que l'élément le plus grand de la colonne ou de la ligne.

Cet exemple utilise FlowColumn pour afficher la liste des desserts Android. Vous pouvez voir la différence entre les largeurs de chaque élément lorsque Modifier.fillMaxColumnWidth() est appliqué aux éléments par rapport à lorsqu'il ne l'est pas, et que les éléments sont encapsulés.

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() appliqué à chaque élément

FillMaxColumnWidth

Aucun changement de largeur défini (encapsulation d'éléments)

Aucune largeur de colonne maximale de remplissage définie