Diseños de flujo en Compose

FlowRow y FlowColumn son elementos componibles similares a Row y Column, pero difieren en que los elementos fluyen a la siguiente línea cuando el contenedor se queda sin espacio. Esto crea varias filas o columnas. La cantidad de elementos en una línea también se puede controlar mediante la configuración de maxItemsInEachRow o maxItemsInEachColumn. A menudo, puedes usar FlowRow y FlowColumn para crear diseños responsivos. El contenido no se cortará si los elementos son demasiado grandes para una dimensión, y usar una combinación de maxItemsInEach* con Modifier.weight(weight) puede ayudar a compilar diseños que llenen o expandan el ancho de una fila o columna cuando sea necesario.

El ejemplo típico es para un chip o una IU de filtrado:

5 chips en un FlowRow, que muestran el desbordamiento a la línea siguiente cuando no hay más espacio disponible.
Figura 1: Ejemplo de FlowRow

Uso básico

Para usar FlowRow o FlowColumn, crea esos elementos componibles y coloca los elementos dentro de ellos que deben seguir el flujo estándar:

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

Este fragmento da como resultado la IU que se muestra arriba, con elementos que fluyen automáticamente a la siguiente fila cuando no hay más espacio en la primera fila.

Características del diseño de flujo

Los diseños de flujo tienen las siguientes funciones y propiedades que puedes usar para crear diferentes diseños en tu app.

Disposición del eje principal: disposición horizontal o vertical

El eje principal es el eje en el que se disponen los elementos (por ejemplo, en FlowRow, los elementos se organizan horizontalmente). El parámetro horizontalArrangement de FlowRow controla la forma en que se distribuye el espacio libre entre los elementos.

En la siguiente tabla, se muestran ejemplos de configuración de horizontalArrangement en elementos para FlowRow:

Se estableció la disposición horizontal en FlowRow

Resultado

Arrangement.Start (Default)

Elementos organizados al inicio

Arrangement.SpaceBetween

Disposición de elementos con espacio intermedio

Arrangement.Center

Elementos organizados en el centro

Arrangement.End

Elementos organizados al final

Arrangement.SpaceAround

Elementos organizados con un espacio a su alrededor

Arrangement.spacedBy(8.dp)

Elementos separados por un dp determinado

Para FlowColumn, hay opciones similares disponibles con verticalArrangement, que tiene el valor predeterminado Arrangement.Top.

Disposición del eje cruzado

El eje cruzado es el eje en la dirección opuesta al eje principal. Por ejemplo, en FlowRow, este es el eje vertical. Si deseas cambiar cómo se organiza el contenido general del contenedor en el eje cruzado, usa verticalArrangement para FlowRow y horizontalArrangement para FlowColumn.

Para FlowRow, en la siguiente tabla, se muestran ejemplos de cómo configurar diferentes verticalArrangement en los elementos:

Disposición vertical establecida en FlowRow

Resultado

Arrangement.Top (Default)

Disposición superior de contenedor

Arrangement.Bottom

Disposición de la parte inferior del contenedor

Arrangement.Center

Disposición de centros de contenedores

Para FlowColumn, hay opciones similares disponibles con horizontalArrangement. La disposición predeterminada del eje cruzado es Arrangement.Start.

Alineación de elementos individuales

Es posible que desees posicionar elementos individuales dentro de la fila con diferentes alineaciones. Esto es diferente de verticalArrangement y horizontalArrangement, ya que alinean elementos dentro de la línea actual. Puedes aplicar esto con Modifier.align().

Por ejemplo, cuando los elementos de una FlowRow tienen alturas diferentes, la fila toma la altura del elemento más grande y les aplica Modifier.align(alignmentOption):

Alineación vertical establecida en FlowRow

Resultado

Alignment.Top (Default)

Elementos alineados en la parte superior

Alignment.Bottom

Elementos alineados en la parte inferior

Alignment.CenterVertically

Elementos alineados en el centro

Para FlowColumn, hay opciones similares disponibles. La alineación predeterminada es Alignment.Start.

Cantidad máxima de elementos en una fila o columna

Los parámetros maxItemsInEachRow o maxItemsInEachColumn definen la cantidad máxima de elementos en el eje principal que se permiten en una línea antes de pasar a la siguiente. El valor predeterminado es Int.MAX_INT, que permite la mayor cantidad de elementos posible, siempre que sus tamaños permitan adaptarse a la línea.

Por ejemplo, configurar un maxItemsInEachRow fuerza el diseño inicial para que solo tenga 3 elementos:

No se estableció ningún máximo

maxItemsInEachRow = 3

No se estableció un máximo en la fila de flujo Cantidad máxima de elementos establecidos en la fila del flujo

Carga diferida de elementos de flujo

ContextualFlowRow y ContextualFlowColumn son una versión especializada de FlowRow y FlowColumn que te permiten cargar de forma diferida el contenido de la fila o columna del flujo. También proporcionan información sobre la posición de los elementos (índice, número de fila y tamaño disponible), por ejemplo, si el elemento está en la primera fila. Esto es útil para grandes conjuntos de datos y si necesitas información contextual sobre un elemento.

El parámetro maxLines limita la cantidad de filas que se muestran, y el parámetro overflow especifica qué se debe mostrar cuando se alcanza un desbordamiento de elementos, lo que te permite especificar un expandIndicator o collapseIndicator personalizados.

Por ejemplo, para mostrar el botón "+ (cantidad de elementos restantes)" o "Mostrar menos", haz lo siguiente:

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

Ejemplo de filas de flujo contextual.
Figura 2: Ejemplo de ContextualFlowRow

Pesos de los artículos

El peso aumenta un elemento en función de su factor y el espacio disponible en la línea en la que se colocó. Es importante destacar que existe una diferencia entre FlowRow y Row con la manera en que se usan los pesos para calcular el ancho de un elemento. Para Rows, el peso se basa en todos los elementos de Row. Con FlowRow, el peso se basa en los elementos de la línea en la que se coloca un elemento, no en todos los elementos del contenedor FlowRow.

Por ejemplo, si tienes 4 elementos que caen todos en una línea, cada uno con diferentes pesos de 1f, 2f, 1f y 3f, el peso total es 7f. El espacio restante en una fila o columna se dividirá por 7f. Luego, el ancho de cada elemento se calculará con weight * (remainingSpace / totalWeight).

Puedes usar una combinación de Modifier.weight y la cantidad máxima de elementos con FlowRow o FlowColumn para crear un diseño similar a una cuadrícula. Este enfoque es útil para crear diseños responsivos que se ajusten al tamaño de tu dispositivo.

Hay algunos ejemplos diferentes de lo que puedes lograr con el uso de pesos. Un ejemplo es una cuadrícula en la que los elementos tienen el mismo tamaño, como se muestra a continuación:

Cuadrícula creada con una fila de flujo
Figura 3: Usa FlowRow para crear una cuadrícula

Para crear una cuadrícula con tamaños de elementos iguales, puedes hacer lo siguiente:

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

Es importante destacar que, si agregas otro elemento y lo repites 10 veces en lugar de 9, el último elemento ocupará toda la última columna, ya que el peso total de toda la fila es 1f:

Tamaño completo del último elemento en la cuadrícula
Figura 4: Cómo usar FlowRow para crear una cuadrícula en la que el último elemento ocupe todo el ancho

Puedes combinar pesos con otros Modifiers, como Modifier.width(exactDpAmount), Modifier.aspectRatio(aspectRatio) o Modifier.fillMaxWidth(fraction). Todos estos modificadores funcionan en conjunto para permitir el ajuste de tamaño responsivo de los elementos dentro de una FlowRow (o FlowColumn).

También puedes crear una cuadrícula alternativa de diferentes tamaños de elementos, en la que dos elementos ocupen la mitad del ancho cada uno y uno ocupe todo el ancho de la siguiente columna:

Cuadrícula alterna con fila de flujo
Figura 5: FlowRow con tamaños de filas alternativos

Puedes lograr esto con el siguiente código:

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

Tamaño fraccionario

Con Modifier.fillMaxWidth(fraction), puedes especificar el tamaño del contenedor que debe ocupar un elemento. Esto difiere del funcionamiento de Modifier.fillMaxWidth(fraction) cuando se aplica a Row o Column, en el sentido de que los elementos Row/Column ocupan un porcentaje del ancho restante, en lugar del ancho completo del contenedor.

Por ejemplo, el siguiente código produce resultados diferentes cuando se usa FlowRow frente a 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 medio con 0.7 fracción del ancho completo del contenedor.

Ancho fraccionario con fila de flujo

Row: El elemento del medio ocupa el 0.7% del ancho restante de Row.

Ancho fraccionario con fila

fillMaxColumnWidth() y fillMaxRowHeight()

Aplicar Modifier.fillMaxColumnWidth() o Modifier.fillMaxRowHeight() a un elemento dentro de FlowColumn o FlowRow garantiza que los elementos de la misma columna o fila tengan el mismo ancho o alto que el elemento más grande de la columna o fila.

En este ejemplo, se usa FlowColumn para mostrar la lista de postres de Android. Puedes ver la diferencia en el ancho de cada elemento cuando se aplica Modifier.fillMaxColumnWidth() a los elementos en comparación con cuando no lo es y cuando se unen los elementos.

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

Se aplicó Modifier.fillMaxColumnWidth() a cada elemento

FillMaxColumnWidth.

No se establecieron cambios de ancho (elementos de unión)

No se configuró el ancho de columna máximo de relleno