Compose 中的 Flow 布局

FlowRowFlowColumn 是与 RowColumn 类似的可组合项,但它们的不同之处在于,当容器空间不足时,项目会流到下一行。这会创建多行或多列。可以通过设置 maxItemsInEachRowmaxItemsInEachColumn 来控制一行中的项目数量。您通常可以使用 FlowRowFlowColumn 来构建响应式布局——如果项目对于一个维度来说太大,内容不会被截断,并且结合使用 maxItemsInEach*Modifier.weight(weight) 可以帮助构建在需要时填充/扩展行或列宽度的布局。

典型的示例是用于芯片或过滤 UI

5 chips in a FlowRow, showing the overflow to the next line when there is no
more space available.
图 1. FlowRow 示例

基本用法

要使用 FlowRowFlowColumn,请创建这些可组合项并将应遵循标准流的项目放置在其中。

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

此代码段会生成上面显示的 UI,当第一行没有更多空间时,项目会自动流到下一行。

流布局的功能

流布局具有以下功能和属性,您可以使用它们在您的应用中创建不同的布局。

主轴排列:水平或垂直排列

主轴是项目布局所在的轴(例如,在 FlowRow 中,项目水平排列)。FlowRow 中的 horizontalArrangement 参数控制项目之间空闲空间的分配方式。

下表显示了在 FlowRow 的项目上设置 horizontalArrangement 的示例

FlowRow 上设置的水平排列

结果

Arrangement.Start (默认)

Items arranged with start

Arrangement.SpaceBetween

Items arrangement with space in between

Arrangement.Center

Items arranged in the center

Arrangement.End

Items arranged at the end

Arrangement.SpaceAround

Items arranged with space around them

Arrangement.spacedBy(8.dp)

Items spaced by a certain dp

对于 FlowColumn,可以使用 verticalArrangement 提供类似的选项,默认值为 Arrangement.Top

交叉轴排列

交叉轴是与主轴方向相反的轴。例如,在 FlowRow 中,这是垂直轴。要更改容器内整体内容在交叉轴上的排列方式,请对 FlowRow 使用 verticalArrangement,对 FlowColumn 使用 horizontalArrangement

对于 FlowRow,下表显示了在项目上设置不同 verticalArrangement 的示例

FlowRow 上设置的垂直排列

结果

Arrangement.Top (默认)

Container top arrangement

Arrangement.Bottom

Container bottom arrangement

Arrangement.Center

Container center arrangement

对于 FlowColumn,可以使用 horizontalArrangement 提供类似的选项。默认的交叉轴排列是 Arrangement.Start

单个项目对齐

您可能希望以不同的对齐方式在行中定位单个项目。这与 verticalArrangementhorizontalArrangement 不同,因为它对齐的是当前行内的项目。您可以使用 Modifier.align() 应用此功能。

例如,当 FlowRow 中的项目高度不同时,该行会采用最高项目的的高度并对项目应用 Modifier.align(alignmentOption)

FlowRow 上设置的垂直对齐

结果

Alignment.Top (默认)

Items aligned to the top

Alignment.Bottom

Items aligned to the bottom

Alignment.CenterVertically

Items aligned to the center

对于 FlowColumn,可以使用类似的选项。默认的对齐方式是 Alignment.Start

行或列中的最大项目数

参数 maxItemsInEachRowmaxItemsInEachColumn 定义主轴上允许在一行中显示的最大项目数,然后再换行。默认值为 Int.MAX_INT,它允许尽可能多的项目,只要它们的尺寸允许它们适合一行即可。

例如,设置 maxItemsInEachRow 会强制初始布局仅包含 3 个项目

未设置最大值

maxItemsInEachRow = 3

No max set on flow row Max items set on flow row

延迟加载流项目

ContextualFlowRowContextualFlowColumnFlowRowFlowColumn 的一个专门版本,允许您延迟加载流行或列的内容。它们还提供有关项目位置(索引、行号和可用大小)的信息,例如项目是否在第一行。这对于大型数据集以及您需要有关项目的上下文信息时很有用。

参数 maxLines 限制显示的行数,参数 overflow 指定达到项目溢出时应显示的内容,允许您指定自定义的 expandIndicatorcollapseIndicator

例如,要显示“+(剩余项目数量)”或“显示较少”按钮

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

Example of contextual flow rows.
图 2. ContextualFlowRow 示例

项目权重

权重会根据项目的因子及其所在行的可用空间来扩展项目。重要的是,FlowRowRow 在使用权重计算项目宽度的方式上存在差异。对于 Rows,权重基于 Row所有项目。对于 FlowRow,权重基于项目所在行的项目,而不是 FlowRow 容器中的所有项目。

例如,如果您有 4 个项目都位于一行上,每个项目的权重分别为 1f、2f、1f3f,则总权重为 7f。行或列中的剩余空间将除以 7f。然后,每个项目的宽度将使用以下公式计算:weight * (remainingSpace / totalWeight)

您可以将 Modifier.weight 和最大项目数与 FlowRowFlowColumn 结合使用来创建网格状布局。这种方法对于创建适应设备大小的响应式布局很有用。

您可以使用权重实现几种不同的示例。一个示例是项目大小相等的网格,如下所示

Grid created with flow row
图 3. 使用 FlowRow 创建网格

要创建项目大小相等的网格,您可以执行以下操作

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

重要的是,如果您添加另一个项目并将其重复 10 次而不是 9 次,则最后一个项目将占据最后一列的整个宽度,因为整行的总权重为 1f

Last item full size on grid
图 4. 使用 FlowRow 创建网格,其中最后一个项目占据完整宽度

您可以将权重与其他 Modifiers 结合使用,例如 Modifier.width(exactDpAmount)、Modifier.aspectRatio(aspectRatio)Modifier.fillMaxWidth(fraction)。这些修饰符协同工作,允许在 FlowRow(或 FlowColumn)中对项目进行响应式大小调整。

您还可以创建交替的网格,其中项目大小不同,两个项目各占据一半宽度,一个项目占据下一列的完整宽度

Alternating grid with flow row
图 5. FlowRow,其中行的大小交替变化

您可以使用以下代码实现此功能

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

分数大小调整

使用 Modifier.fillMaxWidth(fraction),您可以指定项目应占据的容器大小。这与将 Modifier.fillMaxWidth(fraction) 应用于 RowColumn 时有所不同,因为 Row/Column 项目占据剩余宽度的百分比,而不是整个容器的宽度。

例如,以下代码在使用 FlowRowRow 时会产生不同的结果

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:中间项目占据整个容器宽度的 0.7 倍。

Fractional width with flow row

Row:中间项目占据剩余 Row 宽度的 0.7%。

Fractional width with row

fillMaxColumnWidth()fillMaxRowHeight()

Modifier.fillMaxColumnWidth()Modifier.fillMaxRowHeight() 应用于 FlowColumnFlowRow 中的项目,可确保同一列或行中的项目占据该列/行中最大项目的相同宽度或高度。

例如,此示例使用 FlowColumn 显示 Android 甜点的列表。您可以看到在将 Modifier.fillMaxColumnWidth() 应用于项目与未应用且项目换行时,每个项目的宽度差异。

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() 应用于每个项目

fillMaxColumnWidth

未设置宽度更改(项目换行)

No fill max column width set