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,当第一行没有更多空间时,项目会自动流到下一行。

Flow 布局的功能

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

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

主轴是项目排列的轴(例如,在 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

延迟加载 Flow 项目

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

参数 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()

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

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

No fill max column width set