Diseños de flujo en Compose

FlowRow y FlowColumn son elementos componibles que son similares a Row y Column, pero difieren en que fluyan a la línea siguiente cuando el contenedor se quede sin espacio. Esto crea varias filas o columnas. El número de elementos de una línea también se puede controlar. estableciendo maxItemsInEachRow o maxItemsInEachColumn. Puedes usar a menudo FlowRow y FlowColumn para crear diseños responsivos (no se cortará el contenido) si los artículos son demasiado grandes para una dimensión y, si se usa una combinación de maxItemsInEach* con Modifier.weight(weight) puede ayudar a crear diseños que rellenar o expandir 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 siguiente línea 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 colócalos. en su interior que debería 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 genera la IU que se muestra arriba, y los elementos fluyen automáticamente hacia la siguiente fila cuando no haya más espacio en la primera.

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 organizan los elementos (por ejemplo, en FlowRow, los elementos se organizan horizontalmente). El horizontalArrangement en FlowRow controla la forma en que se distribuye el espacio libre entre los elementos.

En la siguiente tabla, se muestran ejemplos de cómo configurar horizontalArrangement en los elementos para FlowRow:

Disposición horizontal establecida en FlowRow

Resultado

Arrangement.Start (Default)

Elementos organizados con inicio

Arrangement.SpaceBetween

Disposición de los elementos con espacio intermedio

Arrangement.Center

Elementos organizados en el centro

Arrangement.End

Elementos organizados al final

Arrangement.SpaceAround

Elementos ordenados con espacio a su alrededor

Arrangement.spacedBy(8.dp)

Elementos espaciados por un dp determinado

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

Disposición en los ejes cruzados

El eje cruzado es el eje en la dirección opuesta al eje principal. Para Por ejemplo, en FlowRow, este es el eje vertical. Para cambiar la forma el contenido dentro del contenedor está organizado 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 sobre la parte superior del contenedor

Arrangement.Bottom

Disposición inferior del contenedor

Arrangement.Center

Disposición en el centro del contenedor

Para FlowColumn, hay opciones similares disponibles con horizontalArrangement. La disposición predeterminada de los ejes cruzados es Arrangement.Start.

Alineación de elementos individuales

Es recomendable que ubiques elementos individuales en la fila con diferentes alineaciones. Es diferente de verticalArrangement y horizontalArrangement, ya que alinea los elementos dentro de la línea actual. Puedes aplica esto con Modifier.align().

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

Alineación vertical establecida en FlowRow

Resultado

Alignment.Top (Default)

Elementos alineados en la parte superior

Alignment.Bottom

Elementos alineados a la parte inferior

Alignment.CenterVertically

Elementos alineados al 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 elementos del eje principal para dejarlos en una línea antes de pasar a la siguiente. El el valor predeterminado es Int.MAX_INT, que permite tantos elementos como sea posible, siempre que sus tamaños les permiten encajar en la línea.

Por ejemplo, establecer una maxItemsInEachRow fuerza el diseño inicial a que solo se adapte tienen 3 elementos:

No se estableció un máximo

maxItemsInEachRow = 3

No se estableció un máximo en la fila de flujo Cantidad máxima de elementos establecidos en la fila de 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 tu fila o columna de flujo. También proporcionan información sobre la posición de los artículos (índice, número de fila y tamaño disponible), por ejemplo, si el elemento se encuentra en el primer lugar 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 produce un desbordamiento de elementos alcances, lo que te permite especificar un expandIndicator personalizado o collapseIndicator

Por ejemplo, para mostrar un signo "+ (cantidad de elementos restantes)" o "Mostrar menos" :

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 contextuales.
Figura 2: Ejemplo de ContextualFlowRow

Pesos del artículo

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 hay una diferencia entre FlowRow y Row con cómo se usan los pesos para calcular el ancho de un elemento. Para Rows, peso se basa en todos los elementos de Row. Con FlowRow, el peso se basa en la los artículos de la línea en la que se coloca un artículo, no todos los elementos de la FlowRow.

Por ejemplo, si tienes 4 elementos que se encuentran 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 calculó 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 adaptables que se ajusten al tamaño de tu dispositivo.

Hay algunos ejemplos diferentes de lo que puedes lograr usando pesos. Uno 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 el mismo tamaño de elementos, 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, la el último elemento ocupa toda la última columna, ya que el peso total de toda la fila es 1f:

Último elemento en tamaño original en la cuadrícula
Figura 4: Usa 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 tamaño responsivo de los elementos dentro de un FlowRow (o FlowColumn).

También puedes crear una cuadrícula alternada con distintos tamaños de elementos, en la que dos elementos ocupan la mitad del ancho cada uno y un elemento ocupa el ancho total de la siguiente Columna:

Cuadrícula alternada con fila de flujo
Figura 5: FlowRow con tamaños de filas alternados

Puedes lograrlo 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 fraccional

Con Modifier.fillMaxWidth(fraction), puedes especificar el tamaño contenedor que un artículo debe ocupar. Esto difiere de cómo Modifier.fillMaxWidth(fraction) funciona cuando se aplica a Row o Column, en que los elementos Row/Column ocupen un porcentaje del ancho restante, en lugar de el ancho de todo el contenedor.

Por ejemplo, el siguiente código produce resultados diferentes cuando se usa FlowRow en comparación con 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 fracciones del ancho completo del contenedor.

Ancho fraccionario con fila de flujo

Row: Elemento medio que ocupa el 0.7% del ancho restante de Row.

Ancho fraccionario con fila

fillMaxColumnWidth() y fillMaxRowHeight()

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

Por ejemplo, este ejemplo usa FlowColumn para mostrar la lista de postres de Android. Puedes ver la diferencia entre el ancho de cada elemento cuando Se aplica Modifier.fillMaxColumnWidth() a los elementos y cuando no para unir 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 (unión de elementos)

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