Layouts de fluxo no Compose

FlowRow e FlowColumn são combináveis semelhantes a Row e Column, mas que diferem nos 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 desativado se os itens forem muito grandes para uma dimensão e usando uma combinação de maxItemsInEach* com Modifier.weight(weight) pode ajudar a criar layouts que preencher/expandir a largura de uma linha ou coluna quando necessário.

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

5 ícones em um 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 posicione os itens dentro dele que deve 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 houver 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 layouts diferentes no seu app.

Organização do eixo principal: organização horizontal ou vertical

O eixo principal é aquele no qual os itens são dispostos (por exemplo, em FlowRow, os itens são organizados horizontalmente). O 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 nos itens para FlowRow:

Organização horizontal definida em FlowRow

Resultado

Arrangement.Start (Default)

Itens organizados com início

Arrangement.SpaceBetween

Itens organizados com espaço no meio

Arrangement.Center

Itens organizados 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 no verticalArrangement, com o padrão de Arrangement.Top.

Arranjo de eixos cruzados

O eixo transversal é o eixo na direção oposta do eixo principal. Para Em FlowRow, este é o eixo vertical. Para alterar a forma como o o conteúdo dentro do contêiner for organizado no eixo cruzado, use verticalArrangement para FlowRow e horizontalArrangement para FlowColumn.

Para FlowRow, a tabela a seguir mostra exemplos de configuração de valores verticalArrangement nos itens:

Organização vertical definida em FlowRow

Resultado

Arrangement.Top (Default)

Disposição da parte superior do contêiner

Arrangement.Bottom

Disposição inferior do contêiner

Arrangement.Center

Disposição do centro do contêiner

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

Você pode querer posicionar itens individuais na linha com diferentes alinhamentos. Ele é diferente de verticalArrangement e horizontalArrangement, porque ela alinha os itens à linha atual. Você pode aplicar isto com Modifier.align().

Por exemplo, quando os itens em uma FlowRow têm alturas diferentes, a linha usa o altura do maior item e aplica Modifier.align(alignmentOption) ao 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:

Máximo de itens na linha ou coluna

Os parâmetros maxItemsInEachRow ou maxItemsInEachColumn definem o itens no eixo principal para permitir em uma linha antes de ir para a próxima. O O padrão é Int.MAX_INT, que permite o maior número possível de itens, desde que o tamanho delas permite que se encaixem na linha.

Por exemplo, definir uma maxItemsInEachRow força o layout inicial a apenas tem 3 itens:

Nenhum 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

O ContextualFlowRow e o ContextualFlowColumn são recursos especializados Versão de FlowRow e FlowColumn que permitem o carregamento lento do conteúdo. da linha ou coluna do fluxo. Eles 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á no primeiro linha de comando. 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 overflow especifica o que será exibido quando for alcançado, permitindo que você especifique um objeto expandIndicator personalizado ou collapseIndicator.

Por exemplo, para mostrar um "+ (número de itens restantes)" ou "Mostrar menos" botão:

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 dele 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 no 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ê tem quatro itens que se enquadram em uma linha, cada um com diferentes de 1f, 2f, 1f e 3f, o peso total é 7f. O espaço restante em uma linha ou coluna serão divididas por 7f. Então, a largura de cada item será calculado usando: weight * (remainingSpace / totalWeight).

Você pode 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 seu dispositivo.

Há alguns exemplos diferentes do que é possível conseguir usando pesos. Um exemplo é uma grade em que os itens têm o mesmo tamanho, como 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)
    }
}

É importante ressaltar que, se você adicionar outro item e repeti-lo dez vezes em vez de nove, o o último item ocupa a última coluna inteira, como o peso total da linha inteira é 1f:

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

É possível combinar pesos com outros Modifiers, como Modifier.width(exactDpAmount), Modifier.aspectRatio(aspectRatio) ou Modifier.fillMaxWidth(fraction) Todos esses modificadores funcionam em conjunto permitem o dimensionamento responsivo de itens em uma 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))
        }
    }
}

Tamanho fracionário

Usando 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, em que os itens Row/Column ocupam uma porcentagem da largura restante, em vez de a largura de todo o contêiner.

Por exemplo, o código abaixo produz resultados diferentes ao usar FlowRow. x 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 0,7 fração 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()

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

Por exemplo, este exemplo usa FlowColumn para exibir a lista de Android sobremesas É 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() aplicado a cada item

fillMaxColumnWidth

Nenhuma alteração de largura definida (agrupamento de itens)

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