Layouts de fluxo no Compose

FlowRow e FlowColumn são elementos combináveis semelhantes a Row e Column, mas diferem porque os itens passam para a próxima linha quando o contêiner fica sem espaço. Isso cria várias linhas ou colunas. O número de itens em uma linha também pode ser controlado definindo maxItemsInEachRow ou maxItemsInEachColumn. Muitas vezes, é possível usar FlowRow e FlowColumn para criar layouts responsivos. O conteúdo não será cortado se os itens forem muito grandes para uma dimensão. Além disso, usar uma combinação de maxItemsInEach* com Modifier.weight(weight) pode ajudar a criar layouts que preenchem/expandem a largura de uma linha ou coluna quando necessário.

O exemplo típico é para um chip ou uma interface de filtragem:

Cinco chips em uma FlowRow, mostrando o estouro para a próxima linha quando não há mais espaço disponível.
Figura 1. Exemplo de FlowRow

Uso básico

Para usar FlowRow ou FlowColumn, crie esses elementos combináveis e coloque os itens dentro dele que devem seguir o fluxo padrão:

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

Esse snippet resulta na interface mostrada acima, com itens fluindo automaticamente para a próxima linha quando não há mais espaço na primeira linha.

Recursos do layout de fluxo

Os layouts de fluxo têm os seguintes recursos e propriedades que você pode usar para criar layouts diferentes no seu app.

Organização do eixo principal: horizontal ou vertical

O eixo principal é aquele em que os itens são dispostos. Por exemplo, em FlowRow, os itens são organizados horizontalmente. O parâmetro horizontalArrangement em FlowRow controla a forma como o espaço livre é distribuído entre os itens.

A tabela a seguir mostra exemplos de como definir horizontalArrangement em itens para FlowRow:

Organização horizontal definida em FlowRow

Resultado

Arrangement.Start (Default)

Itens organizados com início

Arrangement.SpaceBetween

Organização de itens com espaço entre eles

Arrangement.Center

Itens organizados no centro

Arrangement.End

Itens organizados no final

Arrangement.SpaceAround

Itens organizados com espaço ao redor

Arrangement.spacedBy(8.dp)

Itens espaçados por um determinado dp

Para FlowColumn, opções semelhantes estão disponíveis com verticalArrangement, com o padrão de Arrangement.Top.

Organização entre eixos

O eixo cruzado é o eixo na direção oposta ao eixo principal. Por exemplo, em FlowRow, esse é o eixo vertical. Para mudar a forma como o conteúdo geral dentro do contêiner é organizado no eixo cruzado, use verticalArrangement para FlowRow e horizontalArrangement para FlowColumn.

Para FlowRow, a tabela a seguir mostra exemplos de como definir diferentes verticalArrangement nos itens:

Organização vertical definida em FlowRow

Resultado

Arrangement.Top (Default)

Organização da parte de cima do contêiner

Arrangement.Bottom

Organização da parte de baixo do contêiner

Arrangement.Center

Disposição do centro do contêiner

Para FlowColumn, opções semelhantes estão disponíveis com horizontalArrangement. O arranjo padrão no eixo cruzado é Arrangement.Start.

Alinhamento de itens individuais

Talvez você queira posicionar itens individuais na linha com alinhamentos diferentes. Isso é diferente de verticalArrangement e horizontalArrangement, já que alinha os itens na linha atual. Você pode fazer isso com Modifier.align().

Por exemplo, quando os itens em um FlowRow têm alturas diferentes, a linha assume a altura do maior item e aplica Modifier.align(alignmentOption) aos itens:

Alinhamento vertical definido em FlowRow

Resultado

Alignment.Top (Default)

Itens alinhados à parte de cima

Alignment.Bottom

Itens alinhados à parte de baixo

Alignment.CenterVertically

Itens alinhados ao centro

Para FlowColumn, há opções semelhantes disponíveis. O alinhamento padrão é Alignment.Start.

Máximo de itens em linha ou coluna

Os parâmetros maxItemsInEachRow ou maxItemsInEachColumn definem o número máximo de itens no eixo principal que podem aparecer em uma linha antes de serem transferidos para a próxima. O padrão é Int.MAX_INT, que permite o máximo possível de itens, desde que os tamanhos deles permitam que eles se encaixem na linha.

Por exemplo, definir um maxItemsInEachRow força o layout inicial a ter apenas três itens:

Nenhum valor máximo definido

maxItemsInEachRow = 3

Nenhum máximo definido na linha de fluxo Número máximo de itens definidos na linha do fluxo

Pesos dos itens

O peso aumenta um item com base no fator dele e no espaço disponível na linha em que ele foi colocado. É importante notar que há uma diferença entre FlowRow e Row em relação a como os pesos são usados para calcular a largura de um item. Para Rows, o peso é baseado em todos os itens no Row. Com FlowRow, a ponderação é baseada nos itens da linha em que um item é colocado, e não em todos os itens do contêiner FlowRow.

Por exemplo, se você tiver quatro itens que estão em uma linha, cada um com pesos diferentes de 1f, 2f, 1f e 3f, o peso total será 7f. O espaço restante em uma linha ou coluna será dividido por 7f. Em seguida, a largura de cada item será calculada usando: weight * (remainingSpace / totalWeight).

É possível usar uma combinação de Modifier.weight e número máximo de itens com FlowRow ou FlowColumn para criar um layout semelhante a uma grade. Essa abordagem é útil para criar layouts responsivos que se ajustam ao tamanho do seu dispositivo.

Há alguns exemplos diferentes do que você pode fazer usando ponderações. Um exemplo é uma grade em que os itens têm o mesmo tamanho, conforme mostrado abaixo:

Grade criada com linha de fluxo
Figura 2. Como usar FlowRow para criar uma grade

Para criar uma grade de itens de tamanhos iguais, faça o seguinte:

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

É importante lembrar que, se você adicionar outro item e repeti-lo 10 vezes em vez de 9, o último item vai ocupar toda a última coluna, já que o peso total da linha é 1f:

Último item em tamanho real na grade
Figura 3. Usar FlowRow para criar uma grade com o último item ocupando toda a largura

É possível combinar ponderações com outros Modifiers, como Modifier.width(exactDpAmount), Modifier.aspectRatio(aspectRatio) ou Modifier.fillMaxWidth(fraction). Todos esses modificadores funcionam em conjunto para permitir o dimensionamento responsivo de itens em um FlowRow (ou FlowColumn).

Você também pode criar uma grade alternada de itens de tamanhos diferentes, em que dois itens ocupam metade da largura cada um, e um item ocupa toda a largura da próxima coluna:

Grade alternada com linha de fluxo
Figura 4. FlowRow com tamanhos alternados de linhas

Você pode fazer isso com o seguinte 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))
        }
    }
}

Dimensionamento fracionário

Com Modifier.fillMaxWidth(fraction), é possível especificar o tamanho do contêiner que um item deve ocupar. Isso é diferente de como Modifier.fillMaxWidth(fraction) funciona quando aplicado a Row ou Column, já que os itens Row/Column ocupam uma porcentagem da largura restante, em vez da largura total do contêiner.

Por exemplo, o código a seguir produz resultados diferentes ao usar FlowRow em vez de 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: item do meio com fração de 0,7 da largura total do contêiner.

Largura fracionária com linha de fluxo

Row: o item do meio ocupa 0,7% da largura restante de Row.

Largura fracionária com linha

fillMaxColumnWidth() e fillMaxRowHeight()

Aplicar Modifier.fillMaxColumnWidth() ou Modifier.fillMaxRowHeight() a um item dentro de um FlowColumn ou FlowRow garante que os itens na mesma coluna ou linha ocupem a mesma largura ou altura do maior item na coluna/linha.

Por exemplo, este exemplo usa FlowColumn para mostrar a lista de sobremesas do Android. É possível ver a diferença na largura de cada item quando Modifier.fillMaxColumnWidth() é aplicado a eles e quando não é, e os itens são quebrados.

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() aplicado a cada item

fillMaxColumnWidth

Nenhuma mudança de largura definida (itens de quebra de linha)

Nenhuma largura máxima de coluna de preenchimento definida