
从"不知道用哪个布局"说起
HarmonyOS NEXT 开发里,布局容器选型经常让人犯难。官方文档把 Column、Row、Flex、Stack 的 API 列得很清楚,但实际写页面时,到底哪个容器适合什么场景,参数怎么配才能达到预期效果,官方没有给太多指导。
很多刚接触 ArkUI 的人容易犯一个错误:所有页面都用 Column 套 Row,再用一堆固定宽高去硬怼。代码能跑,但一旦要适配不同屏幕尺寸,或者要修改排版,维护成本直接飙升。
这一篇把几个核心容器梳理一遍,结合三个最常见的布局场景,看看实际项目中怎么用更稳。
布局容器的定位与差异
这几个容器解决的问题不同:
| 容器 | 核心特性 | 适用场景 | 不适用场景 |
|---|---|---|---|
| Column | 垂直排列子组件 | 表单、列表、信息流 | 需要横向滚动的内容 |
| Row | 水平排列子组件 | 导航栏、标签栏、按钮组 | 内容垂直排布 |
| Flex | 弹性布局,可控制伸缩 | 等分布局、自适应卡片 | 简单垂直/水平排列时杀鸡用牛刀 |
| Stack | 层叠定位 | 悬浮按钮、遮罩层、水印 | 内容需要回避的布局 |
Stack 的使用频率远低于前三个,但它的层叠能力在某些场景下不可替代。Flex 很多人当进阶版 Row/Column 用,其实它的核心价值在于 flexGrow 和 flexShrink,这两个参数能让布局自适应,减少很多硬编码宽高。
环境说明
text
DevEco Studio 版本:DevEco Studio 6.1.0 及以上
HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上
目标设备:手机
三个核心示例
1. 水平垂直居中 ------ Column + Row 组合
这个需求在界面中很常见:弹窗居中、加载动画居中、欢迎页面居中。
typescript
// 页面居中布局示例
@Entry
@Component
struct CenterLayoutDemo {
build() {
// 外层容器填满屏幕
Column() {
Column() {
Text('居中的内容')
.fontSize(20)
.fontColor(Color.White)
.padding(20)
}
.width(200)
.height(200)
.backgroundColor(0x317AFF)
.borderRadius(12)
.justifyContent(FlexAlign.Center) // 主轴(垂直)居中
.alignItems(HorizontalAlign.Center) // 交叉轴(水平)居中
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
参数说明:
justifyContent:主轴对齐方式。Column 的主轴是垂直方向,传入FlexAlign.Center让子组件在垂直方向居中。alignItems:交叉轴对齐方式。Column 的交叉轴是水平方向,传入HorizontalAlign.Center让子组件在水平方向居中。
注意事项:
- 内外两层都用 Column 是实现整个页面居中的常用写法。如果只在外层设置,子组件内部的内容可能不会居中。
justifyContent和alignItems是针对容器子组件的对齐,不是针对容器自身。
2. 等分布局 ------ Flex + flexGrow
等分布局在底部导航、主页选项卡、多列卡片中很常见。用固定宽度实现等分会导致适配困难,Flex 的弹性伸缩特性专门解决这个问题。
typescript
// 三等分布局示例
@Entry
@Component
struct EqualDivideLayoutDemo {
@State private tabNames: string[] = ['首页', '发现', '我的']
build() {
Column() {
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceEvenly }) {
ForEach(this.tabNames, (item: string, index: number) => {
Column() {
Text(item)
.fontSize(16)
.fontColor(Color.Black)
.lineHeight(40)
}
.width(80) // 固定宽度,不会伸缩
.height(40)
.backgroundColor(0xF5F5F5)
})
}
.width('100%')
.height(40)
.margin({ bottom: 20 })
// 正确的等分布局:使用 flexGrow
Flex({ direction: FlexDirection.Row }) {
ForEach(this.tabNames, (item: string) => {
Column() {
Text(item)
.fontSize(16)
.fontColor(Color.Black)
.lineHeight(40)
}
.layoutWeight(1) // 使用 layoutWeight 实现均匀分配
.height(40)
.backgroundColor(0xE0E0E0)
})
}
.width('100%')
.height(40)
}
.width('100%')
.padding(10)
}
}
参数说明:
layoutWeight:ArkUI 中实现等分更推荐用layoutWeight而不是flexGrow。layoutWeight是 ArkUI 封装的便捷属性,直接按比例分配剩余空间,比手动设置flexGrow更稳定。SpaceEvenly:项目间的间距相等,且首尾也有间距。
为什么推荐 layoutWeight 而不是 flexGrow:
- 实际开发中发现,
flexGrow在嵌套 Flex 容器时,有时会出现计算误差,导致布局错位。layoutWeight的实现更直接,没有额外的折行计算逻辑,稳定性更好。 flexShrink类似,但实际项目中用到的情况较少,除非你明确知道子组件在不同屏幕下会溢出。
3. 层叠布局 ------ Stack
层叠布局用来实现浮层、水印、头像上的红点提示等效果。Stack 的核心能力是让子组件在 Z 轴方向上重叠。
typescript
// 头像+红点提示的层叠布局
@Entry
@Component
struct StackLayoutDemo {
@State private hasNewMessage: boolean = true
build() {
Column() {
Stack() {
// 底层:头像
Column() {
Text('U')
.fontSize(40)
.fontColor(Color.White)
}
.width(80)
.height(80)
.backgroundColor(0x317AFF)
.borderRadius(40)
.alignSelf(ItemAlign.Center)
// 顶层:未读红点
if (this.hasNewMessage) {
Column() {
Text('3')
.fontSize(12)
.fontColor(Color.White)
.lineHeight(18)
.textAlign(TextAlign.Center)
}
.width(18)
.height(18)
.backgroundColor(Color.Red)
.borderRadius(9)
.position({ x: 62, y: -2 }) // 定位到头像右上角
}
}
.width(80)
.height(80)
.margin({ top: 50 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Center)
}
}
参数说明:
position:设置子组件相对于 Stack 容器的偏移。x 和 y 分别表示水平和垂直偏移量。- 红点组件使用
if条件渲染,状态变化时 UI 自动刷新。
注意事项:
- Stack 默认子组件按添加顺序层叠,后添加的在顶层。
position是相对 Stack 容器的坐标,不是相对父组件。如果要实现"相对于某个子组件定位",需要在外层再包一层 Stack。- 红点数据用
@State管理,状态变化时组件会重建,这是 ArkUI 的刷新机制决定的。
常见踩坑
坑1:justifyContent 和 alignItems 在 Column 和 Row 中的方向混淆
现象 :在 Column 中设置 alignItems(HorizontalAlign.Center) 后,子组件水平方向居中了,但在 Row 中设置同样的属性,布局却完全不对。
原因 :ArkUI 的 justifyContent 和 alignItems 方向是跟随容器主轴(main axis)和交叉轴(cross axis)变化的。Column 的主轴是垂直方向,交叉轴是水平方向;Row 正好相反。所以 Row 中要水平居中,需要设置 justifyContent(FlexAlign.Center) 而不是 alignItems。
解法 :写布局前先确认容器的主轴方向。有一个简单的记忆方法:justifyContent 控制的是"排列方向"上的对齐,alignItems 控制的是"垂直排列方向"上的对齐。
坑2:layoutWeight 与 width 同时设置时的优先级问题
现象 :给某个子组件同时设置 layoutWeight(1) 和 width(80),子组件宽度固定为 80,不会均匀分配剩余空间。
原因 :layoutWeight 只会在子组件没有设置固定宽高(或 width/height 为 auto)的情况下才生效。一旦设置了具体数值,layoutWeight 会被忽略。
解法 :使用 layoutWeight 时,不要给子组件设置固定宽度。如果需要预设最小值,可以用 constraintSize 设置最小宽度,但宽度不能写死。
最佳实践
-
不要用 Column/Row 硬编码所有布局 。等分布局优先用
layoutWeight,层叠布局用 Stack,弹性的用 Flex。在build()里频繁使用固定宽高,后续屏幕适配会很痛苦。 -
layoutWeight比flexGrow更稳定 。在嵌套 Flex 的场景下,flexGrow有时会出现百分比计算误差,而layoutWeight直接按比例分配剩余空间,很少出问题。 -
Stack 的
position定位不要过度依赖绝对值 。设备分辨率差异大时,绝对值会偏移。如果需要相对定位(比如红点固定在头像右上角),可以考虑用offset配合百分比,或者在外层包一个 RelativeContainer(HarmonyOS 6.1.0 新增)。
Demo 入口
typescript
// 完整布局示例入口
@Entry
@Component
struct LayoutDemoPage {
build() {
Scroll() {
Column() {
// 示例1:居中布局
CenterLayoutDemo()
Divider().height(2).margin(20)
// 示例2:等分布局
EqualDivideLayoutDemo()
Divider().height(2).margin(20)
// 示例3:层叠布局
StackLayoutDemo()
}
.width('100%')
.padding(10)
}
}
}
FAQ
Q:为什么 Column 里用 justifyContent(FlexAlign.Center) 子组件没有居中?
A:检查一下子组件的高度是否撑满了容器。如果子组件高度超过容器高度,居中不会有视觉上的居中的感觉。建议给容器设置固定高度或者 height('100%')。
Q:layoutWeight 和 flexGrow 效果一样吗?
A:从功能上讲,两者都用于分配剩余空间。但在 ArkUI 中,layoutWeight 是更直接的 ArkTS 属性,底层实现比 flexGrow 更稳定,推荐优先使用 layoutWeight。
Q:Stack 里的子组件可以通过触摸事件区分层级吗?
A:可以。Stack 容器会按子组件的层叠顺序处理触摸事件。默认情况下,顶层组件会先响应触摸。如果需要底层的组件也能响应,可以通过 hitTestBehavior 属性调整,比如 HitTestMode.None 让组件不参与事件检测。
如果你在 ArkUI 布局中遇到其他奇怪的问题,可以重点检查容器的主轴方向,以及
alignSelf、flexGrow这些属性是否被正确覆盖。官方文档的描述偏向 API 层面,实际运行效果往往需要结合真机验证,不同设备的渲染表现可能会有细微差异。