前言

用 Compose 写布局的人,多多少少都经历过这种纠结:
Row和Column,够简单,但不会换行------子项一旦超出容器宽度,直接溢出。- 从
foundation-layout1.4 起,FlowRow和FlowColumn(),终于能换行了,但不支持弹性伸缩------你没办法让某个子项按比例占满剩余空间 - 想要「换行 + 弹性伸缩」?以前只有两条路:要么套
AndroidView调 Google 的老库flexbox-layout(一个 View 体系的 RecyclerView LayoutManager),要么自己写Layout手算。
2026 年 4 月,Compose Foundation 1.9.0-alpha01 正式引入了 FlexBox 布局:
- 一个原生的、声明式的、完整对齐 CSS Flexbox 语义的容器
- 换行、弹性伸缩、对齐控制、间距分配,一个 Composable 全搞定。
今天,从「为什么它必须存在」的设计动机出发,把 FlexBox 的能力边界、与 FlowRow 的本质区别、核心 API 的用法、布局算法的内部流程,一次讲清楚。
Compose FlexBox 是什么
- Compose Foundation 新增的弹性盒子布局容器,完整对齐 CSS Flexbox 规范语义;
- 支持子项在主轴方向上自动换行 (wrap)和弹性伸缩(flex-grow / flex-shrink)。

与 CSS Flexbox 的关系
如果你写过 Web 前端,对 display: flex 一定不陌生。 
Compose FlexBox 的设计目标就是把 CSS Flexbox 的核心能力搬到 Compose 声明式体系中,同时做了 Compose 化的适配:
| CSS Flexbox | Compose FlexBox |
|---|---|
display: flex |
FlexBox { } |
flex-direction |
FlexDirection.Row / Column |
flex-wrap |
FlexWrap.Wrap / NoWrap |
justify-content |
JustifyContent.SpaceBetween 等 |
align-items |
AlignItems.Center 等 |
flex-grow / flex-shrink |
Modifier.flex { grow(1f); shrink(0f) } |
align-self |
Modifier.flex { alignSelf(AlignSelf.Stretch) } |
order |
Modifier.flex { order(2) } |
关键不同 :CSS Flexbox 默认不换行(flex-wrap: nowrap),而 Compose FlexBox 默认就是 Wrap 模式------这是因为移动端屏幕尺寸多变,换行是更安全的默认行为。
FlexBox特性预览
完整对齐 CSS Flexbox 语义,为 Compose 带来 换行 + 弹性伸缩 的原生能力 
FlexBox vs Compose已有方案
三代布局的本质差异如下:
| 能力维度 | Row / Column | FlowRow / FlowColumn | FlexBox |
|---|---|---|---|
| 换行 | ❌ 不支持 | ✅ 自动换行 | ✅ 自动换行 |
| 弹性伸缩 | ❌ 只有 weight |
❌ 不支持 | ✅ flex { grow(); shrink() } |
| 主轴对齐 | Arrangement |
Arrangement |
JustifyContent(更丰富) |
| 交叉轴对齐 | Alignment |
Alignment |
AlignItems + AlignSelf |
| 子项排序 | ❌ | ❌ | ✅ order() |
| 行间距控制 | N/A | maxItemsInEachRow |
自动按 wrap 分行 + gap |
| shrink 溢出收缩 | ❌ | ❌ | ✅ 按比例收缩 |
| 适用场景 | 简单单行/单列 | 标签流、Chip 组 | 自适应卡片、仪表盘、复杂响应式 |
关键差异:weight ≠ flex-grow
很多人以为 Row 里的 Modifier.weight(1f) 就是 flex-grow------不是的 。weight 只在固定数量的子项之间分配固定空间 ,它不参与换行计算。而 flex-grow 是在换行后的每一行内,对剩余空间做二次分配。
举个例子:5 个子项,每个 min-width 200dp,容器宽 1000dp。
Row+weight:5 个全挤在一行,每个刚好 200dp(或等分),不会换行FlowRow:3 个一行、2 个一行,但第一行右侧有空白无法填满FlexBox+grow(1f):3 个一行、2 个一行,每行的剩余空间按 grow 比例自动填满
这就是 FlexBox 存在的核心理由:换行之后,每一行内部还能做弹性伸缩。
核心 API 详解
完整的 FlexBox 配置体系 = 容器级 6 参数 + 子项级 5 属性 
容器级配置
FlexBox 的容器配置通过参数直接传入:
kotlin
@Composable
fun FlexBox(
modifier: Modifier = Modifier,
flexDirection: FlexDirection = FlexDirection.Row,
flexWrap: FlexWrap = FlexWrap.Wrap,
justifyContent: JustifyContent = JustifyContent.FlexStart,
alignItems: AlignItems = AlignItems.Stretch,
alignContent: AlignContent = AlignContent.FlexStart,
gap: Dp = 0.dp,
content: @Composable FlexBoxScope.() -> Unit
)
各参数说明:
flexDirection:主轴方向,Row(水平,默认)或Column(垂直)flexWrap:是否换行,Wrap(默认)或NoWrapjustifyContent:主轴方向的对齐与分布,支持FlexStart/FlexEnd/Center/SpaceBetween/SpaceAround/SpaceEvenlyalignItems:交叉轴方向子项的默认对齐,支持FlexStart/FlexEnd/Center/Stretch/BaselinealignContent:多行场景下行与行之间的对齐方式gap:子项之间的间距(主轴和交叉轴统一)
子项级配置
子项通过 Modifier.flex { } DSL 设置弹性属性:
kotlin
FlexBox(
justifyContent = JustifyContent.SpaceBetween,
alignItems = AlignItems.Center,
gap = 12.dp
) {
// 固定宽度子项
Box(
Modifier
.flex { order(1); shrink(0f) }
.width(80.dp)
.height(80.dp)
.background(Color.Blue)
)
// 弹性填充子项
Text(
"这段文字会占满剩余空间",
Modifier.flex { grow(1f) }
)
// 自定义交叉轴对齐
Icon(
Icons.Default.Star,
contentDescription = null,
Modifier.flex { alignSelf(AlignSelf.FlexEnd) }
)
}
flex { } DSL 速查
| 函数 | 默认值 | 说明 |
|---|---|---|
grow(float) |
0f | 剩余空间分配权重,0 表示不伸展 |
shrink(float) |
1f | 空间不足时的收缩权重,0 表示不收缩 |
basis(Dp) |
Dp.Unspecified |
参与 flex 计算前的初始尺寸 |
order(int) |
0 | 视觉排序优先级,值越小越靠前 |
alignSelf(AlignSelf) |
Auto |
覆盖容器的 alignItems 设置 |
最小可运行示例:自适应标签墙
kotlin
@Composable
fun AdaptiveTagWall(tags: List<String>) {
FlexBox(
flexWrap = FlexWrap.Wrap,
gap = 8.dp,
justifyContent = JustifyContent.FlexStart,
alignItems = AlignItems.Center
) {
tags.forEach { tag ->
Surface(
modifier = Modifier.flex { grow(0f); shrink(1f) },
shape = RoundedCornerShape(20.dp),
color = MaterialTheme.colorScheme.secondaryContainer
) {
Text(
text = tag,
modifier = Modifier.padding(horizontal = 14.dp, vertical = 6.dp),
style = MaterialTheme.typography.labelMedium
)
}
}
}
}
这个标签墙里,每个标签自适应内容宽度,超出容器宽度自动换行。shrink(1f) 保证极端情况下标签可以压缩文字而不溢出。
添加依赖
kotlin
// build.gradle.kts
dependencies {
implementation("androidx.compose.foundation:foundation-layout:1.9.0-alpha01")
}
注意:FlexBox 目前处于 alpha 阶段,API 可能在后续版本中调整。生产环境使用请评估风险。
布局算法
理解内部算法,才能在复杂场景下预测布局行为。Compose FlexBox 的测量流程可以拆成 5 步:

Step 1:计算 Hypothetical Main Size
对每个子项,先用 basis(如果有)或子项自身的内在尺寸,作为假设主轴尺寸。这一步不考虑 grow/shrink,只是确定一个「起点尺寸」。
Step 2:分行(Line Breaking)
按主轴方向依次放置子项。当累计尺寸超过容器主轴长度时,break 到下一行 。这一步的行为取决于 flexWrap:
Wrap:超出就换行NoWrap:全部挤在一行(可能溢出)
Step 3:Flex Resolve(弹性解析)
这是 FlexBox 的核心步骤。对每一行独立执行:
- 如果该行有剩余空间 :按
grow比例分配给标记了grow > 0的子项 - 如果该行空间不足 :按
shrink比例从标记了shrink > 0的子项中回收
这就是 FlowRow 做不到的事------FlowRow 只做分行,不做行内的弹性解析。
Step 4:主轴对齐(Justify)
弹性解析之后,如果行内还有剩余空间(比如所有子项 grow = 0),就按 justifyContent 的策略分配间距:
SpaceBetween:两端顶格,中间等分SpaceAround:每个子项两侧等距SpaceEvenly:所有间隙(包括两端)完全等分
Step 5:交叉轴对齐(Align)
最后一步,确定每个子项在交叉轴上的位置:
- 先看子项有没有
alignSelf,有就用子项自己的 - 没有就用容器的
alignItems默认值 Stretch会把子项撑满行高
这五步跑完,每个子项的 (x, y, width, height) 就全部确定了。
适用场景
什么时候该用 FlexBox,什么时候不该用,怎么用才不踩坑? 
✅ 适合用 FlexBox 的场景
1. 自适应卡片网格
仪表盘、数据面板里的卡片需要「宽度自适应、超出换行、行内等分」。FlexBox + grow(1f) + basis(minWidth) 完美解决。
2. 响应式工具栏 / 导航栏
在不同屏幕尺寸下,工具栏按钮需要「能塞就塞一行,塞不下就换行,但每行按钮要等分」。
3. 混合尺寸的标签 / Chip 流
比如搜索页的热门标签,每个标签宽度不同,需要换行 + 视觉均匀分布。
4. 表单布局
输入框 + 标签的组合,在大屏上一行两列,小屏上一行一列,行内的输入框 grow(1f) 占满剩余空间。
❌ 不适合用 FlexBox 的场景
1. 简单的单行水平/垂直排列
Row / Column 就够了,不需要引入 FlexBox 的额外概念成本。
2. 大量列表数据
FlexBox 不支持懒加载 。如果你有几百上千个子项,请用 LazyVerticalGrid 或 LazyVerticalStaggeredGrid。FlexBox 会一次性测量所有子项。
3. 需要固定行列数的网格
如果你需要「固定 3 列」,LazyVerticalGrid(columns = Fixed(3)) 比 FlexBox 更直观。
最佳实践清单
- 1:总是设
gap,别用padding模拟间距------gap参与 flex 计算,padding不参与 - 2:给弹性子项设
basis,别只依赖内在尺寸------basis是 flex 算法的「起点」,设了才能让 grow/shrink 的计算可预测 - 3:
shrink(0f)保护关键子项------图标、头像这种不应该被压缩的元素,明确标记shrink(0f) - 4:用
order()做响应式重排 ------比在不同屏幕尺寸下用if-else切换子项顺序优雅得多 - 5:注意性能边界 ------子项数量控制在 50 个以内,超过这个量级考虑 LazyLayout 方案
- 6:alpha 阶段做好 API 变更预案------用 wrapper Composable 封装一层,未来 API 变动时只改一处
最后

Compose 的布局能力一直在「够用」和「完备」之间挣扎。FlexBox 是「完备」的最后一块拼图。
它补上的不仅是 flex-grow 和 flex-shrink 这两个 API,而是一整套「容器级约束 + 子项级弹性 + 自动分行」的布局语义。这套语义在 CSS 世界已经被验证了十年,现在终于原生落地到了 Compose。
📎 参考资料
- Jetpack Compose FlexBox 官方文档:developer.android.com/develop/ui/...
- Compose Foundation Layout Release Notes:developer.android.com/jetpack/and...