Layouts de fluxo no Compose

FlowRow e FlowColumn são elementos combináveis semelhantes a Row e Column, mas diferem porque os itens fluem 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. Usar uma combinação de maxItemsInEach* com Modifier.weight(weight) pode ajudar a criar layouts que preencham/expandam a largura de uma linha ou coluna quando necessário.

O exemplo típico é para uma IU de filtro ou ícone:

Cinco ícones em uma FlowRow, mostrando o transbordamento 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 deles que precisam 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 os itens fluindo automaticamente para a próxima linha quando não há mais espaço na primeira.

Recursos do layout de fluxo

Os layouts de fluxo têm os seguintes recursos e propriedades que podem ser usados para criar layouts diferentes no app.

Posiçã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 abaixo 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 dos itens com espaço entre eles

Arrangement.Center

Itens dispostos no centro

Arrangement.End

Itens organizados no final

Arrangement.SpaceAround

Itens dispostos 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 Arrangement.Top.

Disposição entre eixos

O eixo cruzado é o eixo na direção oposta ao eixo principal. Por exemplo, em FlowRow, esse é o eixo vertical. Se quiser 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)

Posicionamento na parte de cima do contêiner

Arrangement.Bottom

Disposição inferior do contêiner

Arrangement.Center

Organização do contêiner no centro

Para FlowColumn, opções semelhantes estão disponíveis com horizontalArrangement. A organização padrão de eixo cruzado é Arrangement.Start.

Alinhamento de itens individuais

É possível posicionar itens individuais na linha com diferentes alinhamentos. Isso é diferente de verticalArrangement e horizontalArrangement, porque alinha os itens na linha atual. Você pode aplicar isso com Modifier.align().

Por exemplo, quando os itens em um FlowRow têm alturas diferentes, a linha usa a altura do item maior 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, opções semelhantes estão disponíveis. O alinhamento padrão é Alignment.Start.

Itens máximos na linha ou coluna

Os parâmetros maxItemsInEachRow ou maxItemsInEachColumn definem o número máximo de itens no eixo principal permitidos em uma linha antes de passar para a próxima. O padrão é Int.MAX_INT, que permite o máximo de itens possível, desde que os tamanhos deles permitam que eles caibam 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 valor máximo definido na linha de fluxo Itens máximos definidos na linha do fluxo

Itens de fluxo com carregamento lento

ContextualFlowRow e ContextualFlowColumn são uma versão especializada de FlowRow e FlowColumn que permitem carregar o conteúdo da linha ou coluna do fluxo de maneira lenta. Eles também fornecem informações sobre a posição dos itens (índice, número de linha e tamanho disponível), como se o item está na primeira linha. Isso é útil para grandes conjuntos de dados e se você precisar de informações contextuais sobre um item.

O parâmetro maxLines limita o número de linhas exibidas, e o parâmetro overflow especifica o que será mostrado quando um overflow de itens for atingido, permitindo que você especifique um expandIndicator ou collapseIndicator personalizado.

Por exemplo, para mostrar um botão "+ (número de itens restantes)" ou "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")
}

Exemplo de linhas de fluxo contextual.
Figura 2. Exemplo de ContextualFlowRow

Pesos do item

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

Por exemplo, se você tiver quatro itens que caem 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 itens máximos 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 dispositivo.

Confira alguns exemplos do que você pode fazer usando pesos. Um exemplo é uma grade em que os itens têm o mesmo tamanho, conforme mostrado abaixo:

Grade criada com a linha do fluxo
Figura 3. Como usar FlowRow para criar uma grade

Para criar uma grade de tamanhos iguais de itens, 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)
    }
}

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

Último item em tamanho original na grade
Figura 4. Usar FlowRow para criar uma grade com o último item ocupando a largura total

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

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

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

Para fazer isso, use 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

Usando Modifier.fillMaxWidth(fraction), é possível especificar o tamanho do contêiner que um item precisa ocupar. Isso é diferente de como Modifier.fillMaxWidth(fraction) funciona quando aplicado a Row ou Column, porque os itens Row/Column ocupam uma porcentagem da largura restante, em vez de todo o 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 que o item maior na coluna/linha.

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

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

fillMaxColumnWidth

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

A largura máxima da coluna de preenchimento não foi definida