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 项占据剩余宽度的百分比,而不是整个容器的宽度。

例如,使用 FlowRow 与使用 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:中间项,占整个容器宽度的 0.7%。

包含流动行的分数宽度

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

带行的小数宽度

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

未设置宽度更改(封装项)

未设置填充列宽度上限