界面丑别怪设计师,先问问你的布局会不会写?——鸿蒙 ArkUI 布局系统完全拆解
本文介绍了鸿蒙ArkUI布局系统的五大核心容器:Column(垂直排列)、Row(水平排列)、Stack(层叠布局)、Flex(弹性布局)和Grid(栅格布局)。文章通过代码示例详细说明了每个容器的适用场景和关键属性,重点分析了Flex布局的主轴方向、对齐方式和响应式特性,并提供了Grid布局的典型实现方案。作者强调合理选择布局容器对界面美观度的重要性,旨在帮助开发者掌握鸿蒙UI布局的核心逻辑,实
大家好,我是[晚风依旧似温柔],新人一枚,欢迎大家关注~
本文目录:
前言
说句大实话,很多鸿蒙界面之所以看起来“说不上来哪不对劲”,问题往往不在配色、不在图标,而在——布局没写明白。
Row/Column 到处乱套、Flex 啥都用 default, Grid 永远只是“平均分三列”,Stack 只用来“叠个悬浮按钮”,搞着搞着你会发现:
代码没多少,页面也不复杂,就是一股“移动网页 2009 年风格”的味儿。
ArkUI 本身其实是非常适合写好看界面的——关键是你要真正理解这几个主力布局容器:
Row、Column、Stack、Flex、Grid。
它们不是“想用哪个用哪个”的关系,而是各司其职,有自己的“专长战场”。
今天这篇,就按你给的大纲,一口气把鸿蒙布局系统从底层逻辑到实战场景拆个干净:
5 大布局容器 → Flex 核心属性 → Grid 实战 → 响应式布局 → 常见 UI 设计模式
写完之后,你应该能做到:
- 拿到一个设计稿,大致能在脑子里“翻译”为 Row/Column/Flex 组合
- 不再对对齐方式一脸懵,不再靠来回调
margin()试错 - 列表、宫格、卡片、悬浮按钮、底部弹框,都有一套顺手的写法
一、5 大布局容器:先搞清谁是“砖”,谁是“水泥”
在 ArkUI 里,你基本离不开这五个布局:
- Column:垂直排列
- Row:水平排列
- Stack:层叠摆放(前后覆盖)
- Flex:弹性布局,更强的轴对齐和换行能力
- Grid:栅格 / 宫格布局
可以这——么粗暴地理解:
| 容器 | 主战场 |
|---|---|
| Column | 一列上下排(设置页、列表、表单) |
| Row | 一行左右排(标题 + 按钮、图标 + 文本) |
| Stack | 叠东西(悬浮按钮、角标、蒙层) |
| Flex | 复杂对齐 / 自适应 / 换行布局 |
| Grid | 宫格、九宫格、商品宫格、图片墙 |
下面先快速过一遍概念,再用代码说话。
1.1 Column:所有“从上往下”的布局都该从它开始
典型用法:
Column() {
Text('主标题')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Text('副标题,介绍一下当前页面的用途')
.fontSize(14)
.opacity(0.6)
.margin({ top: 4 })
Button('下一步')
.margin({ top: 20 })
}
.width('100%')
.padding(16)
特点:
-
默认主轴:垂直方向
-
默认交叉轴:水平居左
-
常用属性:
.justifyContent():主轴方向的对齐(靠上/中/下、平均分布).alignItems():交叉轴的对齐(左/中/右)
1.2 Row:所有“左右排一行”的东西归它管
典型场景:左边 icon,右边标题 + 副标题:
Row() {
Image($rawfile('ic_user.png'))
.width(32).height(32)
.margin({ right: 12 })
Column() {
Text('用户名').fontSize(16)
Text('签名 / 描述文本').fontSize(12).opacity(0.6)
}
}
.width('100%')
.padding(12)
.alignItems(VerticalAlign.Center)
Row 跟 Column 一样,也有:
.justifyContent(FlexAlign.Start/Center/End/SpaceBetween/SpaceAround).alignItems(VerticalAlign.Top/Center/Bottom)
1.3 Stack:当你想把东西叠起来时
比如一个卡片右上角有角标、右下角有悬浮按钮:
Stack() {
// 底层卡片
Column() {
Text('VIP 会员').fontSize(20)
Text('尊享更多特权').fontSize(14).opacity(0.6)
}
.padding(16)
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(12)
// 右上角角标
Text('推荐')
.fontSize(10)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.backgroundColor('#FF5555')
.borderRadius(8)
.align(Alignment.TopEnd)
.margin({ top: 8, right: 8 })
// 右下角悬浮按钮
Button('开通')
.align(Alignment.BottomEnd)
.margin({ bottom: 12, right: 12 })
}
.width('100%')
Stack 的核心就两个字:叠 + 对齐。
谁 .align(Alignment.XXX),谁就跑到哪个角落。
1.4 Flex:Row/Column 的“超进化”
Row/Column 解决的是“一维排列”,Flex 在此基础上给了你:
- 更强的 轴对齐:主轴、交叉轴细粒度控制
- 子元素按比例分配空间(flexGrow/flexShrink)
- 自动换行
语法大概长这样:
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
// 一串 tag,超过一行自动换行
tags.forEach(tag => {
Text(tag)
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.borderRadius(12)
.backgroundColor('#F0F0F0')
.margin({ right: 8, bottom: 8 })
})
}
.width('100%')
.justifyContent(FlexAlign.Start)
.alignItems(ItemAlign.Center)
当你发现 Row/Column 开始写得很吃力、对齐非常难调时,往往就是 Flex 上场的信号。
1.5 Grid:宫格你总不能用 9 个 Row 吧?
Grid 的典型场景:
- 首页功能宫格:4 列 * 2 行
- 图片九宫格
- 商品列表(左图右文、多列宫格)
ArkUI 提供了 Grid + GridItem 这样的组合:
Grid() {
ForEach(this.menus, (item) => {
GridItem() {
Column() {
Image(item.icon).width(32).height(32)
Text(item.title).fontSize(12).margin({ top: 6 })
}
.width('100%')
.height(80)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}, item => item.id.toString())
}
.columnsTemplate('1fr 1fr 1fr 1fr') // 四列
.rowsTemplate('80vp 80vp') // 行高(可选)
.columnsGap(12)
.rowsGap(12)
Grid 的精髓在于:用模板字符串描述列宽 / 行高分配规则。
二、Flex 布局核心属性:真的很像前端 Flexbox,但别全照搬
如果你有 Web 前端经验,那 Flex 这块学习成本会低很多,说白了就是 ArkUI 版 Flexbox。
2.1 FlexDirection:主轴方向
Flex({ direction: FlexDirection.Row }) { ... } // 横排
Flex({ direction: FlexDirection.Column }) { ... } // 竖排
选错方向,一切对齐都跟着乱。
2.2 主轴对齐:justifyContent
值基本一眼就懂:
.justifyContent(FlexAlign.Start) // 靠头
.justifyContent(FlexAlign.Center) // 居中
.justifyContent(FlexAlign.End) // 靠尾
.justifyContent(FlexAlign.SpaceBetween) // 两端对齐,元素间等距
.justifyContent(FlexAlign.SpaceAround) // 元素间及两侧都留间距
示例——标签云居中展示:
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
tags.forEach(tag => {
Text(tag)
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
.backgroundColor('#F5F5F5')
.borderRadius(12)
.margin({ bottom: 8, right: 8 })
})
}
.width('100%')
.justifyContent(FlexAlign.Center)
2.3 交叉轴对齐:alignItems / alignContent
alignItems:一行内的“垂直对齐”alignContent:多行整体在交叉轴上的对齐方式
常见写法:
.alignItems(ItemAlign.Center)
.alignItems(ItemAlign.Start)
.alignItems(ItemAlign.End)
例如:
Flex({ direction: FlexDirection.Row }) {
Image($rawfile('icon.png')).width(24).height(24)
Text('带图标的文本').fontSize(16)
}
.alignItems(ItemAlign.Center) // 图片 & 文本竖直居中
2.4 子元素弹性:flexGrow、flexShrink、flexBasis
有时你会遇到这种需求:
- 左边是标题,右边是按钮,想让标题自动占满剩余空间
- 或者几个块按比例分配宽度(比如 2:1:1)
ArkUI 对子项可以用 .flexGrow() 之类的方式(不同版本 API 细节可能略有变化,这里用语义说明):
Flex({ direction: FlexDirection.Row }) {
Text('一个很长很长的标题')
.flexGrow(1) // 占剩余空间
Button('操作')
}
.width('100%')
再比如:三列按比例分配宽度:
Flex({ direction: FlexDirection.Row }) {
Column() { Text('A') }.flexGrow(2) // 占 2 份
Column() { Text('B') }.flexGrow(1) // 占 1 份
Column() { Text('C') }.flexGrow(1) // 占 1 份
}
.width('100%')
这里的数字是“相对权重”,而不是实际像素。
2.5 wrap:是否自动换行
FlexWrap.NoWrap(默认) / FlexWrap.Wrap / FlexWrap.WrapReverse
比如标签云、选中标签、多选按钮区域,典型写法:
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
this.options.forEach(opt => {
Text(opt.label)
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
.margin({ right: 8, bottom: 8 })
.backgroundColor(opt.selected ? '#333333' : '#F0F0F0')
.fontColor(opt.selected ? '#FFFFFF' : '#333333')
.borderRadius(16)
})
}
.width('100%')
Row/Column 要做这种效果就比较勉强,而 Flex 做起来非常自然。
三、Grid 布局实践:不是只能“平均分三列”的
Grid 的威力完全不比 Flex 小,尤其是宫格类界面。
3.1 基本语法:Grid + GridItem
以一个“功能入口宫格”为例:
@Entry
@Component
struct MenuGridExample {
private menus = [
{ id: 1, title: '扫描', icon: $rawfile('ic_scan.png') },
{ id: 2, title: '收款', icon: $rawfile('ic_pay.png') },
{ id: 3, title: '账单', icon: $rawfile('ic_bill.png') },
{ id: 4, title: '会员', icon: $rawfile('ic_vip.png') },
{ id: 5, title: '设置', icon: $rawfile('ic_setting.png') },
];
build() {
Grid() {
ForEach(this.menus, item => {
GridItem() {
Column() {
Image(item.icon).width(32).height(32)
Text(item.title).fontSize(12).margin({ top: 6 })
}
.width('100%')
.height(80)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}, item => item.id.toString())
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('80vp 80vp')
.columnsGap(12)
.rowsGap(12)
.padding(12)
}
}
解释一下几个关键:
columnsTemplate('1fr 1fr 1fr 1fr'):四列,每列等宽(fraction)rowsTemplate('80vp 80vp'):两行,每行高度 80vp;如果菜单多,还会自动往下加columnsGap / rowsGap:列间距 / 行间距
3.2 不规则宽度:如 1 + 3 栅格
有时你想做类似“左边一个大卡片,右边两个小卡片”的布局:
Grid() {
// 左边大卡片:跨两行
GridItem() {
BigCard()
}.rowSpan(2) // 跨两行
// 右上小卡片
GridItem() {
SmallCard('A')
}
// 右下小卡片
GridItem() {
SmallCard('B')
}
}
.columnsTemplate('2fr 1fr') // 左边宽一点,右边窄一点
.rowsTemplate('120vp 120vp')
.columnsGap(12)
.rowsGap(12)
这类栅格在首页 Banner / 推荐位上特别好用。
3.3 图片九宫格:自动计算行列
假设最多 3 列,小于 3 时自动裁;ArkUI Grid 本身就会根据 items 数量自动铺,你只要重点写好 columnsTemplate 即可:
Grid() {
ForEach(this.photos, (src, index) => {
GridItem() {
Image(src)
.objectFit(ImageFit.Cover)
.borderRadius(6)
}
}, idx => idx.toString())
}
.columnsTemplate('1fr 1fr 1fr')
.rowsGap(4)
.columnsGap(4)
剩下就是按业务决定:
- 1 张图 → 你可能用 Row/Column 做大图
- 2–3 张 → 平均分
- 4 张以上 → Grid 九宫格
四、响应式布局技巧:让你的页面在不同尺寸下不“散架”
鸿蒙现在涉及的设备越来越多:手机、平板、车机、甚至大屏。
如果你还是写死 300vp、400vp 到处乱用,界面在不同尺寸上会非常怪。
4.1 百分比布局:width('100%') 不是摆设
能用相对,就尽量不用绝对。
- 宽度优先用
'100%'、'50%',少用写死350vp - 外边距 / 内边距可以有一些固定值,但核心结构尽量宽度自适应
Column() {
Text('标题').fontSize(20)
Text('内容文本...').fontSize(14).margin({ top: 8 })
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
4.2 Flex + wrap 解决“小屏挤不下的问题”
比如顶部一排操作按钮:
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
['筛选', '排序', '导出', '分享', '更多'].forEach(text => {
Button(text)
.margin({ right: 8, bottom: 8 })
})
}
.width('100%')
小屏幕上会自动折行,大屏幕上则一行排完。
4.3 Grid 动态列数:根据屏宽算列数
你可以根据屏幕宽度动态生成 columnsTemplate:
function getColumnsTemplate(screenWidth: number): string {
const minItemWidth = 100; // 每个卡片最小宽度
const colCount = Math.max(2, Math.floor(screenWidth / minItemWidth));
return Array(colCount).fill('1fr').join(' ');
}
在 aboutToAppear 里算一次,或者监听尺寸变化。
4.4 Breakpoint 思路:用条件分支控制布局方式
比如在窄屏上用单列,宽屏上用左右两列:
@if (this.isWideScreen) {
Row() {
LeftPanel().width('40%')
RightPanel().width('60%')
}.width('100%')
} @else {
Column() {
LeftPanel().width('100%')
Divider().margin({ top: 12, bottom: 12 })
RightPanel().width('100%')
}
}
isWideScreen 可以根据 ScreenUtils.getWidth() 或窗口宽度来判断。
五、常见 UI 设计模式示例:直接给你几套可以搬进项目的
光讲概念不爽,咱们来搞几个实战模板。
5.1 设置页条目:左文右箭头 / 开关
@Component
struct SettingsItem {
@Prop title: string = '';
@Prop desc: string = '';
@Prop showSwitch: boolean = false;
@State checked: boolean = false;
build() {
Row() {
Column() {
Text(this.title)
.fontSize(16)
if (this.desc) {
Text(this.desc)
.fontSize(12)
.opacity(0.6)
.margin({ top: 4 })
}
}
.flexGrow(1)
if (this.showSwitch) {
Toggle({ type: ToggleType.Switch, isOn: this.checked })
.onChange((v) => this.checked = v)
} else {
Image($rawfile('ic_arrow_right.png'))
.width(16).height(16)
}
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.alignItems(VerticalAlign.Center)
}
}
这里有几个布局点:
- Row + 左 Column + 右控件
- 中间 Column
flexGrow(1)撑开,把右侧推到最右边
5.2 卡片信息流:图片 + 文本 + 底部操作
@Component
struct ArticleCard {
@Prop title: string = '';
@Prop summary: string = '';
@Prop cover: Resource;
build() {
Column() {
// 封面
Image(this.cover)
.width('100%')
.height(180)
.objectFit(ImageFit.Cover)
.borderRadius({ topLeft: 12, topRight: 12 })
// 文本部分
Column() {
Text(this.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(this.summary)
.fontSize(13)
.opacity(0.7)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ top: 4 })
Row() {
Text('2.3k 阅读').fontSize(11).opacity(0.6)
Blank().flexGrow(1)
Text('刚刚').fontSize(11).opacity(0.6)
}
.margin({ top: 8 })
}
.padding(12)
}
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 4, color: Color.fromARGB(30, 0, 0, 0) })
}
}
这里就用到了:
- Column 包裹整体
- 内部 Row 做左右对齐 +
Blank().flexGrow(1)撑开中间空白 - 图片用上边缘圆角,整体卡片再包一层圆角
5.3 Tab + 内容区:顶部标签 + Flex 均分
@Entry
@Component
struct TabLayoutExample {
@State current: number = 0;
private tabs: string[] = ['推荐', '热门', '最新'];
build() {
Column() {
// Tab 条
Row() {
this.tabs.forEach((t, index) => {
Column() {
Text(t)
.fontSize(16)
.fontWeight(this.current === index ? FontWeight.Bold : FontWeight.Normal)
if (this.current === index) {
// 底部高亮条
Rect()
.width('60%').height(2).backgroundColor('#007DFF')
.margin({ top: 4 })
}
}
.flexGrow(1)
.alignItems(HorizontalAlign.Center)
.onClick(() => this.current = index)
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 10, bottom: 10 })
// 内容区
if (this.current === 0) {
RecommendList()
} else if (this.current === 1) {
HotList()
} else {
LatestList()
}
}
.width('100%')
.height('100%')
}
}
Row + 每个 Tab flexGrow(1),自然平分宽度。
5.4 底部操作栏 + 内容区域:经典 ButtonBar 布局
@Entry
@Component
struct BottomBarLayout {
build() {
Stack() {
// 内容区
Column() {
// 这里放主内容
Text('页面内容...').margin({ top: 20 })
Blank()
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
// 底部操作栏
Row() {
Text('合计:¥ 299.00')
.fontSize(16)
.fontWeight(FontWeight.Bold)
Blank().flexGrow(1)
Button('去结算')
.width(120)
}
.padding({ left: 16, right: 16, top: 10, bottom: 10 })
.backgroundColor('#FFFFFF')
.align(Alignment.Bottom)
}
.width('100%')
.height('100%')
}
}
Stack 把底部条“压”在内容上面:
- 内容区填满
- 底部 Row
.align(Alignment.Bottom)固定贴底
小结:布局不是“多会几个容器”,而是“知道用谁合适”
写 ArkUI 布局这件事,真的可以一句话概括:
Row/Column 打基础,Stack 解决叠放,Flex 处理复杂对齐,Grid 管好宫格。
当你遇到问题时,可以试着问自己:
- “这是一维的还是二维的?” → 一维大多用 Row/Column/Flex,二维通常 Grid
- “需要换行吗?” → 需要的话 Flex(Grid) 更合适
- “这个状态是叠加的还是平铺的?” → 叠加就 Stack
- “需要按比例划分空间吗?” → Flex 的
flexGrow、Grid 的fr
只要心里有这几条判断线,你的 ArkUI 布局就不会再陷入那种——
“我先随便写个 Column + 一堆 margin,能看就行” 的阶段了。
如果觉得有帮助,别忘了点个赞+关注支持一下~
喜欢记得关注,别让好内容被埋没~
更多推荐




所有评论(0)