Layouts de fluxo no Compose

FlowRow e FlowColumn são elementos combináveis semelhantes a Row e Column, mas com diferença no fluxo de itens 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 ao definir maxItemsInEachRow ou maxItemsInEachColumn. Muitas vezes, você pode 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, e o uso de uma combinação de maxItemsInEach* com Modifier.weight(weight) pode ajudar a criar layouts que preencha/expandam a largura de uma linha ou coluna quando necessário.

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

Cinco ícones em um 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 os elementos combináveis abaixo 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 IU mostrada acima, com os 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 podem ser usados para criar diferentes layouts no seu app.

Disposição do eixo principal: organização 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 maneira como o espaço livre é distribuído entre os itens.

A tabela a seguir mostra exemplos de configuração de horizontalArrangement em itens para FlowRow:

Organização horizontal definida em FlowRow

Resultado

Arrangement.Start (Default)

Itens organizados com início

Arrangement.SpaceBetween

Disposição dos itens com espaço entre eles

Arrangement.Center

Itens organizados no centro

Arrangement.End

Itens organizados no final

Arrangement.SpaceAround

Itens organizados com um 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 de eixo cruzado

O eixo cruzado é o eixo na direção oposta ao eixo principal. Por exemplo, em FlowRow, esse é o eixo vertical. Para mudar 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 configuração de verticalArrangement diferentes nos itens:

A organização vertical foi definida em FlowRow

Resultado

Arrangement.Top (Default)

Disposição da parte superior do contêiner

Arrangement.Bottom

Disposição da parte inferior do contêiner

Arrangement.Center

Disposição do centro de contêineres

Para o FlowColumn, opções semelhantes estão disponíveis no horizontalArrangement. A organização padrão de eixos cruzados é Arrangement.Start.

Alinhamento de item individual

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

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

Alinhamento vertical definido em FlowRow

Resultado

Alignment.Top (Default)

Itens alinhados à parte superior

Alignment.Bottom

Itens alinhados à parte inferior

Alignment.CenterVertically

Itens alinhados no centro

Há opções semelhantes disponíveis para FlowColumn. O alinhamento padrão é Alignment.Start.

Máximo de itens na linha ou coluna

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

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

Nenhum valor máximo definido

maxItemsInEachRow = 3

Não há um limite máximo definido na linha do fluxo Número máximo de itens definido na linha do fluxo

Itens do fluxo de carregamento lento

ContextualFlowRow e ContextualFlowColumn são uma versão especializada de FlowRow e FlowColumn que permitem o carregamento lento do conteúdo da linha ou coluna do fluxo. Elas também fornecem informações sobre a posição dos itens (índice, número da linha e tamanho disponível), por exemplo, 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 precisa ser exibido quando um excesso de itens é atingido. Isso permite que você especifique um expandIndicator ou collapseIndicator personalizado.

Por exemplo, para exibir 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 na 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 da linha em que um item está posicionado, não em todos os itens no contêiner FlowRow.

Por exemplo, se você tiver quatro itens 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. Então, a largura de cada item será calculada usando: weight * (remainingSpace / totalWeight).

Você pode usar uma combinação de Modifier.weight e 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 dispositivo.

Há alguns exemplos diferentes do que pode ser alcançado usando pesos. Um exemplo é uma grade em que os itens têm o mesmo tamanho, conforme mostrado abaixo:

Grade criada com a linha de 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)
    }
}

É importante ressaltar que, 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 de toda a linha é 1f:

Tamanho original do último item na grade
Figura 4. Como usar FlowRow para criar uma grade com o último item ocupando toda a largura.

Você pode combinar pesos 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 uma FlowRow (ou FlowColumn).

Você também pode criar uma grade alternada de diferentes tamanhos de itens, 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

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 o Modifier.fillMaxWidth(fraction), é possível especificar o tamanho do contêiner que um item vai 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 de todo o contêiner.

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

Largura fracionária com linha de fluxo

Row: item do meio ocupando 0,7% da largura restante do Row.

Largura fracionária com linha

fillMaxColumnWidth() e fillMaxRowHeight()

A aplicação de Modifier.fillMaxColumnWidth() ou Modifier.fillMaxRowHeight() a um item dentro de uma FlowColumn ou FlowRow garante que os itens na mesma coluna ou linha ocupe a mesma largura ou altura que o maior item na coluna/linha.

Por exemplo, este exemplo usa FlowColumn para exibir a lista de sobremesas do Android. É possível ver a diferença nas larguras de cada item quando Modifier.fillMaxColumnWidth() é aplicado a eles em comparação com quando não é e os itens são unidos.

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

A opção "Modifier.fillMaxColumnWidth()" foi aplicada a cada item

fillMaxColumnWidth

Nenhuma mudança de largura definida (agrupamento de itens)

Nenhuma largura máxima de preenchimento da coluna foi definida