Compose 中的流式布局

FlowRowFlowColumnRowColumn 类似,但不同之处在于,当容器空间不足时,项会流入下一行。这会创建多行或多列。您还可以通过设置 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()

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

例如,此示例使用 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

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

未设置填充最大列宽