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 和项数上限与 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() 应用于项与不应用于项且项换行时每个项宽度的差异。

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

未设置宽度更改(封装内容)

未设置填充最大列宽