Compose 中的流式布局

FlowRowFlowColumn 是与 RowColumn 类似的可组合项,但不同之处在于,当容器空间用尽时,项会流向下一行。这会创建多行或多列。您还可以通过设置 maxItemsInEachRowmaxItemsInEachColumn 来控制行中的项数量。您通常可以使用 FlowRowFlowColumn 构建自适应布局。如果项对于某个维度而言过大,内容不会被截断,而将 maxItemsInEach*Modifier.weight(weight) 结合使用有助于构建可根据需要填充/展开行或列宽度的布局。

典型的示例如下所示,适用于条状标签或过滤界面:

FlowRow 中的 5 个条状标签,显示了当没有可用空间时,条状标签会溢出到下一行。
图 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")
    }
}

此代码段会生成上图所示的界面,当第一行没有空间时,项会自动流向下一行。

流式布局的功能

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

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

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

下表显示了为 FlowRow 对项设置 horizontalArrangement 的示例:

已在“FlowRow”中设置水平排列

结果

Arrangement.Start (Default)

按开始时间排列的项

Arrangement.SpaceBetween

项目之间有空格的排列方式

Arrangement.Center

内容项居中排列

Arrangement.End

在末尾排列的项

Arrangement.SpaceAround

排列有间距的项

Arrangement.spacedBy(8.dp)

按特定 dp 分隔的项

对于 FlowColumnverticalArrangement 提供了类似的选项,默认值为 Arrangement.Top

交叉轴排列

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

对于 FlowRow,下表显示了为各项设置不同 verticalArrangement 的示例:

FlowRow 上设置的垂直排列方式

结果

Arrangement.Top (Default)

容器顶部排列

Arrangement.Bottom

容器底部排列

Arrangement.Center

容器中心排列

对于 FlowColumnhorizontalArrangement 提供了类似的选项。默认的横轴排列方式为 Arrangement.Start

个别内容的对齐方式

您可能希望在行中使用不同的对齐方式来放置各个项。这与 verticalArrangementhorizontalArrangement 不同,因为它会对齐当前行中的项。您可以使用 Modifier.align() 应用此设置。

例如,当 FlowRow 中的项具有不同的高度时,该行会接受最大项的高度,并将 Modifier.align(alignmentOption) 应用于这些项:

已为 FlowRow 设置垂直对齐

结果

Alignment.Top (Default)

内容项顶部对齐

Alignment.Bottom

内容项底部对齐

Alignment.CenterVertically

内容项居中对齐

对于 FlowColumn,也有类似的选项供您选择。默认的对齐方式为 Alignment.Start

行或列中的项数上限

参数 maxItemsInEachRowmaxItemsInEachColumn 用于定义主轴中在一行中允许的最大项数,超出此数目后会换行。默认值为 Int.MAX_INT,此选项允许尽可能多的项,只要它们的大小能容纳在队列中即可。

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

未设置上限

maxItemsInEachRow = 3

未在“流”行上设置上限 在流程行上设置的项数上限

延迟加载流程项

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

情境流程行示例。
图 2. ContextualFlowRow 示例

商品重量

权重会根据其系数和放置在其所在行上的可用空间来放大项。重要的是,使用权重计算内容宽度的方式 FlowRowRow 有所不同。对于 Rows,重量取决于 Row 中的所有项。对于 FlowRow,权重取决于放置项的行中的项,而不是 FlowRow 容器中的所有项。

例如,如果您有 4 个项目都位于同一行,并且每个项目的权重分别为 1f, 2f, 1f3f,则总权重为 7f。行或列中的剩余空间将被除以 7f。然后,系统会使用 weight * (remainingSpace / totalWeight) 计算每项内容的宽度。

您可以将 Modifier.weight 和 max 项与 FlowRowFlowColumn 组合使用,以创建网格状布局。此方法对于创建可根据设备大小调整的响应式布局非常有用。

以下是一些使用权重可实现的不同示例。例如,网格中的项大小相同,如下所示:

使用流程行创建了网格
图 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

网格上最后一项完整尺寸的内容
图 4. 使用 FlowRow 创建一个网格,其中最后一个项占据整个宽度

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

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

交替网格与流式行
图 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 部分。

带流行的小数宽度

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

带有行的按比例宽度

fillMaxColumnWidth()fillMaxRowHeight()

FlowColumnFlowRow 中的项应用 Modifier.fillMaxColumnWidth()Modifier.fillMaxRowHeight() 可确保同一列或行中的项与列/行中的最大项具有相同的宽度或高度。

例如,此示例使用 FlowColumn 显示 Android 甜点列表。您可以看到,在对项应用 Modifier.fillMaxColumnWidth() 与不应用 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

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

未设置填充最大列宽