Row / Column 解决的是“沿一个方向排”。但在真实页面里,总有一类组件数量不稳定:筛选标签、兴趣标签、快捷入口、关键字 chips……它们的共同点是:
- 文案长度不固定(会撑开宽度)。
- 数量不固定(可能 3 个,也可能 30 个)。
- 需要在窄屏自动换行,在宽屏尽量铺满。
这就是 Flex 的主战场。
本文只解决一个问题:做一个可复用的“筛选标签区”,标签会自动换行,且在不同屏宽下能维持稳定的对齐与间距。 不覆盖:瀑布流卡片(更适合 Grid)、长列表虚拟化(List)、自定义布局算法与动画排布(属于后续工程化主题)。
先明确我们要的 UI 行为
一个简单的场景:搜索结果页的筛选条(多标签、可点、可换行)。
窄屏:一行放不下 → 自动换行(wrap)
宽屏:仍保持行内间距一致,不挤成一团(spacing / justify)
关键点不在“怎么画一个按钮”,而在“容器怎么分配空间”。
ArkUI:用 Flex + Wrap 写筛选标签区
先看 ArkUI 版本。核心是两件事:
1. Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) 让子组件可以换行。 2. 用 .margin(...) 给每个标签一个稳定的外边距(比在容器上“猜 gap”更直观)。
@Component
struct FilterChips {
private chips: string[] = ['推荐', '最新', '最热', '可下载', '已购买', '含折扣', '可退款', '48 小时内更新']
build() {
Flex({
direction: FlexDirection.Row,
wrap: FlexWrap.Wrap,
justifyContent: FlexAlign.Start,
alignItems: ItemAlign.Center,
alignContent: FlexAlign.Start
}) {
ForEach(this.chips, (label: string) => {
Text(label)
.fontSize(12)
.fontColor('#111827')
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.backgroundColor('#F3F4F6')
.borderRadius(999)
.margin({ right: 10, bottom: 10 })
})
}
.width('100%')
}
}

读这段代码时可以只盯住 3 个属性:
wrap: FlexWrap.Wrap:决定“放不下就换行”。如果你看到一堆标签被压缩成不可读的样子,第一反应先看 wrap。justifyContent:决定每一行在主轴(横向)怎么分布。想“靠左挤在一起”用Start;想“尽量拉开”用SpaceBetween。alignContent:决定多行整体在交叉轴(纵向)怎么摆放(多数情况下Start就够用)。
> 经验:先把 wrap 跑通,再谈“怎么铺满”。很多页面的真实目标是“可读 + 可点”,不是“强行对齐成一条直线”。
Android(Compose):对应的写法是什么?
在 Android 侧,遇到“可换行的 chips”,最常见有两条路:
- Compose:用
FlowRow(把“换行”交给布局容器)。 - View/XML:用 Google 的
FlexboxLayout(把“换行 + 对齐”交给容器实现)。
下面是 Compose 的思路(伪代码级别,重点在布局意图):
FlowRow(
mainAxisSpacing = 10.dp,
crossAxisSpacing = 10.dp
) {
chips.forEach { label ->
Text(
text = label,
modifier = Modifier
.clip(RoundedCornerShape(999.dp))
.background(Color(0xFFF3F4F6))
.padding(horizontal = 12.dp, vertical = 8.dp)
)
}
}
你会发现:ArkUI 的 .margin({ right, bottom }),在 Compose 侧通常用容器 spacing 来表达;两端本质一样——先让容器具备换行能力,再定义稳定间距。
一张对比表:Flex / FlowRow / FlexboxLayout 要对齐的不是“名字”,是“行为”
| 你要的行为 | ArkUI(Flex) | Android Compose | Android View/XML |
|---|---|---|---|
| 一行放不下自动换行 | wrap: FlexWrap.Wrap |
FlowRow |
FlexboxLayout(wrap) |
| 每行靠左 / 拉开分布 | justifyContent(FlexAlign.Start/SpaceBetween) |
Arrangement.Start/SpaceBetween(思路一致) |
justifyContent(概念一致) |
| 多行整体如何堆叠 | alignContent(FlexAlign.Start/Center/...) |
Alignment / 容器约束 |
alignContent(概念一致) |
| 子项纵向对齐 | alignItems(ItemAlign.Center/Start) |
Alignment.CenterVertically |
alignItems(概念一致) |
| 间距稳定(可读/可点) | 子项 .margin(...) |
mainAxisSpacing/crossAxisSpacing |
layout_margin / item decorator |
如果你把“API 名字对上”当目标,会越写越乱;把“行为对上”当目标,迁移就会变得很线性。
常见坑:为什么我写了 Flex 还是不好用?
1) 把“铺满”当成第一目标 筛选标签区的第一目标是“可读、可点、可扫视”。SpaceBetween 虽然能“拉开”,但当某行只有 2 个标签时,会造成视觉割裂。通常先用 Start + 稳定间距。
2) 标签被压到看不清 如果你看到标签文字被挤压、换行不符合预期,先确认:你是不是还在用 Row?或者 Flex 里没开 wrap。
3) 把“多端适配”留到最后 今天 Android 侧官方已经明确 UI 开发进入 “Compose-first”,并持续把多形态适配(窗口、折叠屏、平板)推到主线能力里。对应到布局层,像这种“可换行 + 可扩展布局”区域,越早用正确的容器表达,后面越不需要返工。
参考素材(当天高质量链接)
- HarmonyOS 官方文档:弹性布局(Flex)
- https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-layout-development-flex-layout
- Android Developers Blog:Android UI Development is Compose First
- https://android-developers.googleblog.com/2026/05/android-ui-development-is-compose-first.html
- Android 官方文档:Adaptive apps(多形态与响应式 UI 的入口)
- https://developer.android.com/develop/adaptive-apps
// Kai@CodeHubble // 观测坐标:Android-HarmonyOS/2026-05-20