Mises en page de flux dans Compose

FlowRow et FlowColumn sont des composables semblables à Row et Column, mais diffèrent en ce sens que les éléments passent à la ligne suivante lorsque le conteneur est à court d'espace. Cela crée plusieurs lignes ou colonnes. Le nombre d'éléments d'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 coupé si les éléments sont trop grands pour une dimension. 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 un FlowRow, montrant 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 génère l'UI illustrée ci-dessus, avec des éléments qui passent automatiquement à la ligne suivante lorsqu'il n'y a plus d'espace sur la première ligne.

Caractéristiques de la mise en page en flux

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

Disposition de l'axe principal: horizontale ou verticale

L'axe principal est l'axe 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 sur des éléments pour FlowRow:

Disposition horizontale définie sur FlowRow

Résultat

Arrangement.Start (Default)

Éléments organisés par début

Arrangement.SpaceBetween

Disposition des éléments avec un espace entre eux

Arrangement.Center

Éléments disposés au centre

Arrangement.End

Éléments disposés à la fin

Arrangement.SpaceAround

Éléments disposés avec de l'espace autour d'eux

Arrangement.spacedBy(8.dp)

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

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

Disposition des axes croisés

L'axe transversal est l'axe opposé à l'axe principal. Par exemple, dans FlowRow, il s'agit de l'axe vertical. Pour modifier l'organisation globale du contenu dans le conteneur sur l'axe transversal, utilisez verticalArrangement pour FlowRow et horizontalArrangement pour FlowColumn.

Pour FlowRow, le tableau suivant présente des exemples de définition de différents verticalArrangement sur les éléments:

Disposition verticale définie sur FlowRow

Résultat

Arrangement.Top (Default)

Disposition du haut du conteneur

Arrangement.Bottom

Disposition du bas du conteneur

Arrangement.Center

Disposition des conteneurs au centre

Pour FlowColumn, des options similaires sont disponibles avec horizontalArrangement. L'arrangement des axes croisés par défaut est Arrangement.Start.

Alignement des éléments individuels

Vous pouvez positionner des éléments individuels dans la ligne avec des alignements différents. Cette option diffère 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'un 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 colonne

Les paramètres maxItemsInEachRow ou maxItemsInEachColumn définissent le nombre maximal d'éléments de l'axe principal autorisés sur une ligne avant le retour à la ligne suivante. La valeur par défaut est Int.MAX_INT, qui autorise autant d'éléments que possible, à condition que leurs tailles leur permettent de tenir sur la ligne.

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

Aucune valeur maximale définie

maxItemsInEachRow = 3

Aucune limite maximale définie sur la ligne du flux Nombre maximal d'éléments défini sur la ligne du flux

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

ContextualFlowRow et ContextualFlowColumn sont une version spécialisée de FlowRow et FlowColumn qui vous permet de charger de manière différée le contenu de votre ligne ou colonne de flux. Ils fournissent également des informations sur la position des éléments (indice, numéro de ligne et taille disponible), par exemple si l'élément se trouve dans la première ligne. Cette fonctionnalité est utile pour les grands ensembles de données 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é lorsqu'un débordement d'éléments est atteint, ce qui vous permet de spécifier un expandIndicator ou un collapseIndicator personnalisé.

Par exemple, pour afficher un bouton "+ (nombre d'articles 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 contextuel.
Figure 2. Exemple de ContextualFlowRow

Poids des articles

Le poids augmente un élément en fonction de son facteur et de l'espace disponible sur la ligne sur 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 éléments du Row. Avec FlowRow, le poids est basé sur les éléments de la ligne dans laquelle un élément est placé, et non sur tous les éléments du conteneur FlowRow.

Par exemple, si vous avez quatre éléments qui se trouvent tous sur une ligne, chacun avec des pondérations différentes de 1f, 2f, 1f et 3f, la pondération totale est de 7f. L'espace restant dans une ligne ou une colonne sera divisé par 7f. Ensuite, la largeur de chaque élément sera calculée à l'aide de: weight * (remainingSpace / totalWeight).

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

Voici quelques exemples de ce que vous pouvez faire avec des poids. Par exemple, 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 d'éléments de taille égale, 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)
    }
}

Notez que si vous ajoutez un autre élément et le répétez 10 fois au lieu de 9, le dernier élément occupe toute la dernière colonne, car le poids total de 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 dont le dernier élément occupe toute la largeur

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

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

Grille alternée avec ligne de flux
Figure 5 : FlowRow avec des tailles de lignes alternées

Pour ce faire, exécutez 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 fractionnaire

Avec Modifier.fillMaxWidth(fraction), vous pouvez spécifier la taille du conteneur que l'élément doit occuper. Cela diffère de la façon dont Modifier.fillMaxWidth(fraction) fonctionne lorsqu'il est appliqué à Row ou Column, car les éléments Row/Column occupent un pourcentage de la largeur restante, plutôt que la largeur de l'ensemble du conteneur.

Par exemple, le code suivant produit des résultats différents lorsque vous utilisez FlowRow par rapport à 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 une fraction de 0,7 de la largeur totale du conteneur.

Largeur fractionnaire avec ligne de flux

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

Largeur fractionnaire avec ligne

fillMaxColumnWidth() et fillMaxRowHeight()

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

Par exemple, cet exemple utilise FlowColumn pour afficher la liste des desserts Android. Vous pouvez voir la différence de largeur de chaque élément lorsque Modifier.fillMaxColumnWidth() est appliqué aux éléments et lorsqu'il ne l'est pas et que les éléments sont mis en page.

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

Aucune modification de largeur définie (éléments en ligne)

Aucune largeur de colonne maximale de remplissage définie