鸿蒙原生 ArkTS 布局体系完全实战指南:从 Flex/Stack/Grid 到 List/Tabs 的 13 种布局模式


一、引言
HarmonyOS NEXT 是鸿蒙生态的里程碑版本,实现了从系统底层到应用框架的完全自研。与之配套的 ArkUI 框架提供了一套完整的声明式 UI 布局体系,其核心设计理念是「弹性」——布局容器和子组件能够根据可用空间自动调整尺寸和排列方式,从而适配从智能手表到折叠屏的各种屏幕尺寸。
ArkUI 的布局体系可归纳为五大类容器,覆盖了移动端开发的所有布局需求:
| 容器类别 | 核心容器 | 排列维度 | 核心属性 |
|---|---|---|---|
| 弹性容器 | Flex / Column / Row |
一维(水平/垂直) | flexGrow / flexShrink / flexBasis / flexWrap |
| 层叠容器 | Stack |
Z 轴(层叠) | alignContent / alignSelf / zIndex |
| 网格容器 | Grid |
二维(行+列) | columnsTemplate / rowsTemplate / columnStart |
| 列表容器 | List |
一维(滚动) | listDirection / divider / sticky |
| 标签容器 | Tabs |
视图切换 | barPosition / scrollable / animationDuration |
本文将深入这五大类容器的 13 种布局模式,包含完整的代码示例、布局公式、典型应用场景和性能对比。
二、项目架构与入口
2.1 应用入口 EntryAbility
鸿蒙应用继承 UIAbility,在 onWindowStageCreate 中加载首页面:
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/TabsBottomDemo', (err) => {
if (err.code) {
hilog.error(0x0000, 'App', 'Failed: %{public}s', JSON.stringify(err));
return;
}
});
}
}
loadContent 参数为页面路径(不含 .ets 后缀),框架自动解析并加载对应页面。
2.2 页面通用骨架
每个演示页面遵循一致的结构:
Column(height:100%, width:100%)
├── 标题栏(蓝底白字,固定)
└── Scroll 滚动区
└── Column
├── SectionCard 演示块 1
├── SectionCard 演示块 2
├── ...
└── TechPanel 技术说明
三、Column 主轴对齐
3.1 核心概念
Column 是最基础的垂直布局容器。主轴(Main Axis)方向为「从上到下」,交叉轴(Cross Axis)方向为「从左到右」:
justifyContent→ 控制主轴(垂直)方向的对齐alignItems→ 控制交叉轴(水平)方向的对齐
3.2 6 种对齐值
| 枚举值 | 效果 | CSS 类比 |
|---|---|---|
FlexAlign.Start |
顶部起始排列 | flex-start |
FlexAlign.Center |
垂直居中 | center |
FlexAlign.End |
底部对齐 | flex-end |
FlexAlign.SpaceBetween |
两端对齐 | space-between |
FlexAlign.SpaceAround |
子项左右间距相等 | space-around |
FlexAlign.SpaceEvenly |
子项及两端间距相等 | space-evenly |
3.3 代码实现
@Entry
@Component
struct ColumnStartPage {
build() {
Column() {
// 标题区
Column() { /* ... */ }
.backgroundColor('#2d5f8a')
// 核心:Column + justifyContent(Start)
Column() {
ForEach(this.infoList, (item, idx) => {
InfoCard({ item, index: idx })
})
}
.alignItems(HorizontalAlign.Start) // 交叉轴左对齐
.justifyContent(FlexAlign.Start) // ★ 主轴从顶部开始 ★
.layoutWeight(1) // 撑满剩余高度
}
.width('100%').height('100%')
.backgroundColor('#eef2f7')
}
}
关键:layoutWeight(1) 让 Column 填满父容器剩余高度,否则 justifyContent 的效果不可见——当容器高度等于内容高度时,Start、Center、End 的差异看不出来。
四、Flex 弹性容器与四种方向
Flex 是比 Column/Row 更底层、更灵活的弹性容器。Column 等效于 flexDirection(Column) 的 Flex,Row 等效于 flexDirection(Row) 的 Flex。
4.1 四种方向
| 方向 | 排列 | 代码首项位置 | 典型场景 |
|---|---|---|---|
FlexDirection.Row |
从左→右 | 最左侧 | 水平导航栏 |
FlexDirection.RowReverse |
从右→左 | 最右侧 | RTL 语言 |
FlexDirection.Column |
从上→下 | 最顶部 | 表单列表 |
FlexDirection.ColumnReverse |
从下→上 | 最底部 | 聊天列表 |
4.2 RowReverse 示例
Flex() {
Text('A(先写)')
Text('B(中间)')
Text('C(后写)')
}
.flexDirection(FlexDirection.RowReverse)
// 显示结果:[C] [B] [A] ← 代码先写的 A 在最右侧
4.3 ColumnReverse 聊天列表
Flex() {
ChatBubble({ msg: '早上好!', isSelf: false }) // 最早 → 顶部
ChatBubble({ msg: '下午三点开会.', isSelf: true })
ChatBubble({ msg: '好的收到。', isSelf: false }) // 最新 → 底部
}
.flexDirection(FlexDirection.ColumnReverse)
容易混淆的点:
- RowReverse 只反转排列顺序,不反转每个组件内部的内容方向
justifyContent(Start)在 RowReverse 下等效于「右侧对齐」alignItems不受反向影响,始终控制交叉轴方向
五、Flex + Wrap 响应式卡片流
当子组件数量多到一行放不下时,flexWrap(FlexWrap.Wrap) 让子组件自动换行到下一行,形成流式布局。
5.1 核心代码
Flex() {
ForEach(PRODUCTS, (product) => {
ProductCard({ product, cardSizeMode: this.cardSizeMode })
})
}
.flexDirection(FlexDirection.Row) // 主轴水平
.flexWrap(FlexWrap.Wrap) // ★ 允许换行 ★
.justifyContent(FlexAlign.Start) // 行内左对齐
.alignItems(ItemAlign.Start) // 交叉轴顶部对齐
.width('100%')
5.2 layoutWeight 等分宽度
子组件使用 layoutWeight(1) 控制行内等分宽度:
@Component
struct ProductCard {
@Link cardSizeMode: number; // 0=小(3列) / 1=中(2列) / 2=大(1列)
build() {
Column() {
Text(this.product.emoji).fontSize(this.cardSizeMode === 2 ? 36 : 26)
Text(this.product.name).fontSize(this.cardSizeMode === 2 ? 18 : 14)
Text(`¥${this.product.price}`).fontSize(this.cardSizeMode === 2 ? 22 : 18)
}
.layoutWeight(1) // ★ 同一行中等分宽度 ★
.margin(6)
}
}
5.3 flexGrow vs layoutWeight
| 属性 | 分配对象 | 公式 |
|---|---|---|
flexGrow |
剩余空间(容器 − 所有子项内容尺寸) | 内容宽 + 剩余空间 × (growN / Σgrow) |
layoutWeight |
容器全部空间 | 容器宽 × (weightN / Σweight) |
在换行布局中,layoutWeight 更常用——每行独立计算权重分配,不受其他行子项内容宽度的干扰。
5.4 @State 密度切换
@State cardSizeMode: number = 0;
Row() {
ForEach([0, 1, 2], (mode: number) => {
Button(['紧凑(3列)', '适中(2列)', '宽大(1列)'][mode])
.layoutWeight(1)
.backgroundColor(this.cardSizeMode === mode ? '#2d5f8a' : '#e8ecf0')
.onClick(() => { this.cardSizeMode = mode; })
})
}
六、Flex + flexGrow 弹性增长
flexGrow 是弹性布局最重要的单个属性,它定义了子组件在剩余空间中的分配比例。
6.1 核心公式
子项 N 的增长量 = 剩余空间 × (growN ÷ Σgrow)
剩余空间 = 容器主轴尺寸 − 所有子组件内容尺寸之和
6.2 单项填充示例
Flex() {
Text('A:grow(0)') // 不增长
Text('B:grow(0)') // 不增长
Text('C:grow(1)') // ★ 填充所有剩余空间 ★
}
.flexDirection(FlexDirection.Row)
.width('100%')
这是「固定左 + 固定右 + 弹性中间」布局模式的实现原理。
6.3 等比分配
// 1:1:1 → 三等分
grow(1) : grow(1) : grow(1) // 每项 = 剩余空间 × 1/3
// 1:2:1 → 中间是两边 2 倍
grow(1) : grow(2) : grow(1) // A=剩余×1/4, B=剩余×1/2, C=剩余×1/4
6.4 垂直方向页面骨架
Flex() {
Row() {}.flexGrow(0).height(48) // 顶部固定
Column() {}.flexGrow(1) // ★ 内容区弹性 ★
Row() {}.flexGrow(0).height(40) // 底部固定
}
.flexDirection(FlexDirection.Column)
.height('100%')
flexGrow(1) 让中间内容区自动填满头尾之外的所有剩余高度,适配从 iPhone SE 到折叠屏的任何屏幕高度。
七、Flex + flexBasis 基准尺寸
flexBasis 设置子组件在弹性分配前的主轴初始尺寸,flexGrow/flexShrink 在此之上叠加。
7.1 最终尺寸公式
最终尺寸 = flexBasis + 增长量(grow) − 压缩量(shrink)
7.2 flexBasis 取值
| 值 | 含义 | 示例 |
|---|---|---|
'auto' |
由内容或 width/height 决定 | .flexBasis('auto') |
'80px' |
固定初始尺寸 80vp | .flexBasis('80px') |
'33%' |
初始尺寸为容器主轴尺寸的 33% | .flexBasis('33%') |
7.3 三种实战模式
// ★ 模式1:固定左栏 + 弹性右栏
Flex() {
Column() {}.flexBasis('80px').flexGrow(0) // 侧栏固定
Column() {}.flexBasis('auto').flexGrow(1) // 主区弹性
}
.flexDirection(FlexDirection.Row)
// ★ 模式2:等百分比
Flex() {
Column() {}.flexBasis('33.3%').flexGrow(0)
Column() {}.flexBasis('33.3%').flexGrow(0)
Column() {}.flexBasis('33.3%').flexGrow(0)
}
// ★ 模式3:垂直骨架
Flex() {
Row() {}.flexBasis('56px').flexGrow(0) // 顶栏
Column() {}.flexBasis('auto').flexGrow(1) // 内容弹性
Row() {}.flexBasis('48px').flexGrow(0) // 底栏
}
.flexDirection(FlexDirection.Column).height('100%')
7.4 flexBasis + flexShrink 压缩
当容器宽度不足以容纳所有子项的 flexBasis 之和时,flexShrink 控制压缩比例:
Flex() {
Box({ basis:'100px', shrink:0 }) // 不缩小,保持100px
Box({ basis:'100px', shrink:1 }) // 缩小
Box({ basis:'100px', shrink:1 }) // 缩小
Box({ basis:'100px', shrink:1 }) // 缩小
}
.flexDirection(FlexDirection.Row)
.width(300) // 4×100=400 > 300,溢出100px
// A:100px, B/C/D 各约 67px
八、Stack 层叠布局
Stack 是 Z 轴层叠容器——子组件沿 Z 轴堆叠而非沿 X/Y 轴排列。后写的子组件覆盖在先写的上方。alignContent 控制所有子组件整体的对齐位置。
8.1 9 种 Alignment
| 水平\垂直 | Top | Center | Bottom |
|---|---|---|---|
| Start | TopStart 左上 |
Start 左中 |
BottomStart 左下 |
| Center | TopCenter 上中 |
Center 正中 |
BottomCenter 下中 |
| End | TopEnd 右上 |
End 右中 |
BottomEnd 右下 |
8.2 TopStart(左上对齐)
Stack() {
Box({ w:200, h:150, label:'底层' })
Box({ w:160, h:100, label:'中层' })
Box({ w:100, h:80, label:'顶层' })
}
.alignContent(Alignment.TopStart)
8.3 Top(顶部居中)
Stack() {
Row() {}.width('100%').height(64).backgroundColor('#2d5f8a') // 背景
Text('📢 系统通知').fontSize(16).fontColor('#ffffff') // 居中标题
Text('✕').alignSelf(ItemAlign.End).margin({ top:8, right:14 }) // 右上关闭
}
.alignContent(Alignment.Top)
8.4 Bottom(底部居中)
Stack() {
Column() {}.width('100%').height(160).backgroundColor('#1a1a2e')
Row() { Text('🎵'), Text('歌名') }
Row() { Text('⏮'), Text('⏸'), Text('⏭') }
}
.alignContent(Alignment.Bottom)
8.5 Overlay(层叠盖层 + ZIndex)
Stack() {
ImageScreen() // L1: 图片(zIndex默认0)
Column() {}.width('100%').height('100%')
.backgroundColor('#22000000').zIndex(1) // L2: 半透明蒙层
Text('👟 运动鞋').alignSelf(ItemAlign.Start)
.margin(10).zIndex(2) // L3: 左上标签
Row() { Text('¥ 1,299').fontColor('#e74c3c') }
.alignSelf(ItemAlign.End).width('100%')
.zIndex(5) // L4: 底部价格条(最上层)
}
.alignContent(Alignment.TopStart)
ZIndex 规则:数值越大越靠前;相同值时后写覆盖先写;无 ZIndex 默认值为 0。
8.6 alignSelf 覆盖整体对齐
Stack() {
Box('A:跟随') // 默认跟随 TopStart
Box('B:左上').alignSelf(ItemAlign.Start) // 覆盖为左上
Box('C:右上').alignSelf(ItemAlign.End) // 覆盖为右上
}
.alignContent(Alignment.Top)
8.7 position 精准定位
Stack() {
Dot({ x:'10%', y:'10%', label:'A' })
Dot({ x:'50%', y:'30%', label:'B' })
Dot({ x:'80%', y:'60%', label:'C' })
}
.alignContent(Alignment.TopStart)
.position({x, y}) 以 Stack 左上角为原点,支持像素和百分比。
九、Grid 网格布局
Grid 是 ArkUI 最强大的二维布局容器,通过 columnsTemplate 和 rowsTemplate 同时控制行和列的尺寸。
9.1 固定列数
Grid() {
ForEach(items, (item) => {
GridItem() {
Column() { Text(item.label) }.width('100%').height(64)
}
})
}
.columnsTemplate('1fr 1fr 1fr') // ★ 三列等宽 ★
.columnsGap(8).rowsGap(8)
.width('100%')
9.2 fr 单位详解
| 模板 | 含义 |
|---|---|
'1fr 1fr 1fr' |
三列等宽 |
'1fr 2fr 1fr' |
中间列是两侧 2 倍 |
'80px 1fr 1fr' |
第一列固定 + 后两列等分剩余 |
'1fr 1fr 1fr 1fr' |
四列等宽 |
9.3 自适应列数
CSS 的 repeat(auto-fill, minmax(...)) 在 ArkUI 中通过代码实现:
@State adaptiveCols: string = '1fr 1fr 1fr';
private minColWidth: number = 100; // 最小列宽 (vp)
Grid() { /* ... */ }
.columnsTemplate(this.adaptiveCols)
.onAreaChange((_oldW, _oldH, newW, _newH) => {
// ★ 自适应公式 ★
let n = Math.floor(newW / this.minColWidth);
n = Math.max(1, Math.min(n, 8));
this.adaptiveCols = Array(n).fill('1fr').join(' ');
})
当容器宽度 360vp 时 n=3(3列),480vp 时 n=4(4列),250vp 时 n=2(2列)。
9.4 rowsTemplate 行定义
// 3×3 九宫格
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr')
// 头尾固定 + 中间弹性(页面骨架)
.columnsTemplate('1fr')
.rowsTemplate('80px 1fr 80px')
// 日历网格(表头固定 + 日期等分)
.columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr') // 7 列
.rowsTemplate('40px 1fr 1fr 1fr 1fr 1fr') // 表头+5行
9.5 GridItem 跨列
Grid() {
// 横幅:跨 3 列(占整行)
GridItem() { Banner() }
.columnStart(1).columnEnd(3) // ★ 跨第1到第3列 ★
// 普通卡片
ForEach(PRODUCTS, (p) => {
GridItem() { ProductCard(p) }
})
}
.columnsTemplate('1fr 1fr 1fr')
十、List 列表容器
List 是鸿蒙的高性能长列表容器,支持按需渲染——只渲染可见区域内的列表项。与 Scroll + Column 的全量渲染不同,List 在数据量大时依然流畅。
10.1 垂直列表
List() {
ForEach(messages, (msg) => {
ListItem() {
// 列表项内容
Row() {
Text(msg.avatar).fontSize(24)
Column() {
Text(msg.name).fontSize(15)
Text(msg.message).fontSize(13).fontColor('#666')
}
}.padding(12)
}
}
}
.listDirection(Axis.Vertical) // 垂直方向(默认)
.divider({ strokeWidth: 0.5, color: '#e8ecf0', startMargin: 72 })
.width('100%').height(320)
10.2 水平列表
List() {
ForEach(PRODUCTS, (product) => {
ListItem() {
ProductCard(product)
}
.width(160) // ★ 水平 ListItem 必须设宽度 ★
.margin({ left: 6, right: 6 })
}
}
.listDirection(Axis.Horizontal) // ★ 水平方向 ★
.height(180)
.scrollBar(BarState.Off) // 隐藏滚动条
.edgeEffect(EdgeEffect.Spring) // 边缘回弹
10.3 粘性标题
// 通讯录分组:标题粘性吸顶
ForEach(groups, (group) => {
ListItem() {
Text(group.initial) // 分组标题
}
.sticky(Sticky.Normal) // ★ 粘性效果 ★
ForEach(group.contacts, (c) => {
ListItem() { ContactRow(c) }
})
})
10.4 List vs Scroll+Column 选择指南
| 数据量 | 推荐方案 | 原因 |
|---|---|---|
| < 10 项 | Scroll+Column 或 List | 均可 |
| 10~50 项 | List | 按需渲染,内存更低 |
| 50~1000+ 项 | List + LazyForEach | 必须使用,否则卡顿 |
十一、Tabs 标签页容器
Tabs 是 ArkUI 中最常用的多视图切换容器,由 TabBar(标签栏)+ TabContent(内容区)组成。
11.1 顶部标签
Tabs() {
TabContent() { HomePage() }.tabBar('🏠 首页')
TabContent() { DiscoverPage() }.tabBar('🔍 发现')
TabContent() { MessagePage() }.tabBar('💬 消息')
TabContent() { MinePage() }.tabBar('👤 我的')
}
.barPosition(BarPosition.Start) // ★ 标签栏在顶部 ★
.width('100%').height(300)
11.2 底部标签(主流 App 导航模式)
Tabs() {
ForEach(this.tabs, (tab, idx) => {
TabContent() {
PageContent(tab.icon, tab.label)
}.tabBar(this.BottomTabBuilder(idx, tab))
})
}
.barPosition(BarPosition.End) // ★ 标签栏在底部(核心)★
.barHeight(60) // 自定义标签栏高度
.width('100%').height('100%')
11.3 自定义底部 Tab(图标+文字+角标)
@Builder
BottomTabBuilder(index: number, tab: TabData) {
Column() {
Stack() {
Text(tab.icon).fontSize(22)
.fontColor(this.currentIndex === index ? '#2d5f8a' : '#999')
if (tab.badge > 0) {
Text(`${tab.badge}`).fontSize(9).fontColor('#ffffff')
.backgroundColor('#e74c3c').borderRadius(8)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
}
}.width(30).height(26)
Text(tab.label).fontSize(10)
.fontColor(this.currentIndex === index ? '#2d5f8a' : '#999')
.margin({ top: 2 })
}
}
11.4 中间突出按钮
if (tab.isCenter) {
// ★ 中间突出按钮 ★
Column() { Text('➕').fontSize(28).fontColor('#ffffff') }
.width(52).height(52)
.backgroundColor('#e74c3c').borderRadius(26)
.offset({ y: -12 }) // 向上突出 TabBar
} else {
// 普通标签
Column() { Text(tab.icon), Text(tab.label) }
}
11.5 Tabs 常用属性速查
| 属性 | 作用 | 示例 |
|---|---|---|
barPosition |
标签栏位置 | Start(顶部) / End(底部) |
scrollable |
可滑动切换 | true / false |
animationDuration |
切换动画时长(ms) | 300 |
barWidth |
标签栏宽度 | '100%' |
barHeight |
标签栏高度 | 48 / 60 |
onChange |
切换回调 | (idx) => {} |
十二、容器选择决策树
面对一个 UI 需求时,可以按照以下决策树选择合适的容器:
需要「视图切换」(多页)?
├── 是 → Tabs
│ ├── 主界面导航 → barPosition(End) 底部
│ └── 内容分类 → barPosition(Start) 顶部
└── 否 → 继续
需要「层叠/覆盖」效果?
├── 是 → Stack
│ ├── 图片叠文字 → alignContent(TopStart) + alignSelf
│ ├── 头像+徽章 → alignContent(TopStart) + alignSelf(End)
│ └── 多层层叠 → ZIndex 控制顺序
└── 否 → 继续
需要「二维网格」?
├── 是 → Grid
│ ├── 固定列数 → columnsTemplate('1fr 1fr 1fr')
│ ├── 自适应列数 → @State + onAreaChange
│ └── 固定行高 → rowsTemplate('80px 1fr 80px')
└── 否 → 继续
需要「滚动长列表」?
├── 是 → List
│ ├── 垂直列表 → listDirection(Vertical)
│ ├── 水平列表 → listDirection(Horizontal)
│ └── 数据量>50 → List + LazyForEach
└── 否 → 继续 → Flex / Column / Row
├── 一维排列 → Flex + flexDirection
│ ├── 从左到右 → Row / FlexDirection.Row
│ ├── 从右到左 → FlexDirection.RowReverse
│ ├── 从上到下 → Column / FlexDirection.Column
│ └── 从下到上 → FlexDirection.ColumnReverse
├── 需要弹性增长 → flexGrow
├── 需要初始尺寸 → flexBasis
├── 需要换行 → flexWrap(Wrap)
└── 需要等分 → layoutWeight
十三、性能优化建议
13.1 避免深层嵌套
Flex/Stack/Grid/List 可以嵌套,但深度建议控制在 3-4 层以内。过深的布局树会增加每次状态变更时的计算开销。
13.2 @State 最小化作用域
@State 的变化会触发所在组件及其子组件的 build() 重渲染。尽量将状态作用域限制在真正需要更新的层级。
13.3 长列表用 List
数据量超过 20 项时必须用 List 而非 Scroll + Column,因为 List 只渲染可见区域,Scroll + Column 全量渲染。
13.4 阴影与圆角的性能
过多的 shadow 和 borderRadius 会影响滚动帧率。建议:
- 长列表卡片使用轻柔阴影(
color: '#10000000') - 对焦点卡片才使用重阴影
13.5 懒加载 LazyForEach
当 List 或 Grid 中的子项超过 50 项时,使用 LazyForEach 替代 ForEach:
LazyForEach(this.dataSource, (item) => {
ListItem() { ComplexCard(item) }
}, (item) => item.id.toString())
十四、常见布局陷阱
14.1 flexGrow 不生效
原因:容器没有设置明确的主轴尺寸(width/height),
或容器尺寸刚好等于子项内容尺寸(无剩余空间)
解决:确保容器有明确的主轴尺寸,或用 layoutWeight
14.2 子项溢出容器
原因:所有子项的 flexShrink 都是 0(默认 shrink=1,但可被覆盖)
解决:给需要压缩的子项设置 flexShrink(1)
14.3 Wrap 不换行
原因:flexWrap 默认为 NoWrap
解决:显式设置 .flexWrap(FlexWrap.Wrap)
14.4 Stack 方向混淆
误区:Stack(TopStart) 中所有子项一定在左上角
正解:alignSelf 可突破整体对齐约束
记忆法:alignContent = 整体对齐,alignSelf = 个体突破
14.5 ColumnReverse 下 Start/End 方向
误区:Start 仍然是「顶部」
正解:ColumnReverse 下 Start = 底部,End = 顶部
记忆法:Start 永远指向主轴的起始位置
十五、布局属性速查表
15.1 Flex 弹性属性
| 属性 | 作用于 | 默认值 | 说明 | CSS 类比 |
|---|---|---|---|---|
flexGrow |
子组件 | 0 | 放大比例(≥0) | flex-grow |
flexShrink |
子组件 | 1 | 缩小比例(≥0) | flex-shrink |
flexBasis |
子组件 | 'auto' |
主轴初始尺寸 | flex-basis |
flexWrap |
容器 | NoWrap |
换行控制 | flex-wrap |
justifyContent |
容器 | Start |
主轴对齐(6种) | justify-content |
alignItems |
容器 | Stretch |
交叉轴对齐(4种) | align-items |
layoutWeight |
子组件 | 0 | 比例分配全部空间 | 无 |
15.2 Stack 层叠属性
| 属性 | 作用 | 取值 |
|---|---|---|
alignContent |
子组件整体对齐 | 9 种 Alignment |
alignSelf |
单子项突破约束 | ItemAlign.Start/Center/End |
zIndex |
Z 轴顺序 | number(越大越靠前) |
position |
左上角原点定位 | {x, y} 百分比或像素 |
15.3 Grid 网格属性
| 属性 | 作用 | 示例 |
|---|---|---|
columnsTemplate |
列宽模板 | '1fr 1fr 1fr' |
rowsTemplate |
行高模板 | '80px 1fr 80px' |
columnsGap |
列间距 | 8 |
rowsGap |
行间距 | 8 |
columnStart/End |
GridItem 跨列 | number(从1开始) |
rowStart/End |
GridItem 跨行 | number(从1开始) |
15.4 List 列表属性
| 属性 | 作用 | 示例 |
|---|---|---|
listDirection |
滚动方向 | Axis.Vertical / Axis.Horizontal |
divider |
分隔线 | {strokeWidth, color, startMargin} |
sticky |
粘性标题 | Sticky.Normal |
scrollBar |
滚动条 | BarState.Auto / Off |
edgeEffect |
边缘效果 | EdgeEffect.Spring |
15.5 Tabs 标签属性
| 属性 | 作用 | 示例 |
|---|---|---|
barPosition |
标签栏位置 | BarPosition.Start / End |
scrollable |
滑动切换 | true / false |
animationDuration |
切换动画(ms) | 300 |
barHeight |
标签栏高度 | 60 |
十六、结语
从最简单的 Column + justifyContent 基础对齐,到 Flex 四种方向与弹性伸缩,再到 Stack 的九种层叠对齐与 ZIndex 控制,从 Grid 的固定列与自适应列网格,到 List 的高性能长列表,最后到 Tabs 的顶部/底部标签导航——这 13 种布局模式覆盖了鸿蒙应用开发中 95% 以上的界面布局需求。
回顾这些布局模式,可以归纳出三条核心设计哲学:
-
永远不用固定尺寸。能用
flexGrow、layoutWeight、flexBasis的地方,就不写width(100)或height(200)——弹性布局的本质是让空间分配自适应。 -
拥抱「剩余空间」思维。
flexGrow分配剩余空间,layoutWeight重算全部空间,flexBasis定义初始起点。理解这三个概念,弹性布局就不再是魔法。 -
选对容器,事半功倍。Flex 用于一维排列,Stack 用于层叠覆盖,Grid 用于二维网格,List 用于长列表滚动,Tabs 用于多视图切换——每个容器有自己最擅长的领域。
鸿蒙原生 ArkTS 的弹性布局体系,为开发者提供了一套完备、高效、声明式的 UI 布局方案。掌握这些模式后,布局不再是代码的堆砌,而是对屏幕空间的优雅分配。
更多推荐



所有评论(0)