鸿蒙原生ArkTS布局方式之Column+Divider分割线布局


鸿蒙原生 ArkTS 布局实战:Column 系列布局从入门到精通
适用平台: HarmonyOS NEXT 6.1.1(API 24)
开发语言: ArkTS(声明式 UI)
项目类型: Stage 模型,单 HAP 工程
本文源码: 完整项目位于 DevEco Studio 工作区,可直接编译运行
一、引言
HarmonyOS NEXT 作为鸿蒙生态的里程碑版本,彻底剥离了 Android 兼容层,实现了全栈自研。其声明式 UI 框架 ArkTS 借鉴了现代前端框架的设计理念,同时融合了原生移动开发的性能优势。对于从 Android(XML 布局 / Jetpack Compose)或 iOS(SwiftUI)转型而来的开发者而言,ArkTS 的布局体系既有熟悉的影子,也有鸿蒙独有的设计哲学。
在 ArkTS 的众多布局容器中,Column 是最基础、最常用的纵向布局容器。毫不夸张地说,Column 承担了 ArkTS 页面中 80% 以上的纵向布局需求。本文将围绕 Column 及其配套组件,通过一个完整的示例项目,深入剖析 Column 的弹性布局(layoutWeight)、分割线布局(Divider)、间距控制(space)等核心能力,帮助读者彻底掌握鸿蒙原生布局。
二、项目结构概览
在开始编码之前,我们先了解一个标准 HarmonyOS NEXT 工程的结构:
MyApplication3/
├── AppScope/ # 应用级配置
│ ├── app.json5 # bundleName、版本号等
│ └── resources/
├── entry/ # 模块目录
│ ├── src/main/ets/
│ │ ├── entryability/EntryAbility.ets # Ability 入口
│ │ └── pages/
│ │ ├── Index.ets # 主页面(导航入口)
│ │ ├── ColumnExpandedDemo.ets # layoutWeight 弹性布局
│ │ └── ColumnDividerDemo.ets # Divider 分割线布局
│ ├── src/main/resources/
│ │ └── base/profile/main_pages.json # 页面路由注册
│ └── build-profile.json5
├── build-profile.json5 # 应用级构建配置
├── oh-package.json5 # 包管理
└── hvigor/ # 构建工具配置
2.1 构建配置解读
在 build-profile.json5 中,我们定义了 SDK 版本和目标平台:
{
"app": {
"products": [
{
"name": "default",
"targetSdkVersion": "6.1.0(23)",
"compatibleSdkVersion": "6.1.0(23)",
"runtimeOS": "HarmonyOS"
}
]
}
}
注意: HarmonyOS NEXT 的 SDK 版本号格式为
major.minor.patch(API#)。API 24 对应 6.1.1,是 6.1.0 的迭代版本,在布局能力上保持一致,但修复了若干Expanded组件的遗留问题。本文所有示例均兼容 API 24。
2.2 页面路由注册
每个页面必须在 main_pages.json 中注册,否则 router.pushUrl 无法找到目标:
{
"src": [
"pages/Index",
"pages/ColumnExpandedDemo",
"pages/ColumnDividerDemo"
]
}
三、ArkTS 声明式基础:@Entry 与 @Component
3.1 装饰器体系
ArkTS 使用装饰器(Decorator)来标记组件和页面,这是声明式 UI 的核心机制:
| 装饰器 | 作用 | 使用位置 |
|---|---|---|
@Entry |
标记页面入口,表示该组件是一个独立页面 | 只能修饰 @Component |
@Component |
标记一个自定义组件,可被复用 | 结构体 |
@Builder |
标记构建函数,用于封装重复的 UI 片段 | 结构体方法 |
@State |
声明响应式状态变量,变化时自动刷新 UI | 组件属性 |
@Prop |
父传子的单向数据绑定 | 子组件属性 |
3.2 页面入口的完整骨架
import router from '@ohos.router';
@Entry
@Component
struct Index {
build() {
Column() {
// 页面内容在这里构建
}
.width('100%')
.height('100%');
}
}
关键约束:
build()方法必须返回且只能返回一个根组件- 根组件通常是
Column、Row、Flex或Stack - 链式调用是 ArkTS 的标配写法,每个属性方法返回
this以便继续链式调用
四、Column 布局深度解析
4.1 Column 的基本行为
Column 容器沿**主轴(纵轴)从上到下排列子组件,沿交叉轴(横轴)**控制对齐方式。其核心属性如下:
Column() {
// 子组件列表
}
.width('100%') // 容器宽度
.height('100%') // 容器高度
.justifyContent(FlexAlign.Center) // 主轴对齐方式
.alignItems(HorizontalAlign.Center) // 交叉轴对齐方式
.space(8) // 子组件间距
.backgroundColor('#F5F5F5'); // 背景色
主轴对齐(justifyContent)选项:
| FlexAlign 枚举值 | 效果 |
|---|---|
Start |
从上到下排列(默认) |
Center |
垂直居中 |
End |
从下到上排列 |
SpaceBetween |
两端对齐,子组件间间距相等 |
SpaceAround |
每个子组件两侧间距相等 |
SpaceEvenly |
所有间距(含两端)完全相等 |
交叉轴对齐(alignItems)选项:
| HorizontalAlign 枚举值 | 效果 |
|---|---|
Start |
左对齐 |
Center |
水平居中(默认) |
End |
右对齐 |
4.2 Column 与 Row 的对比
Column 和 Row 是 ArkTS 中最重要的两个线性布局容器,它们共享相同的属性体系,仅在主轴方向上不同:
| 特性 | Column | Row |
|---|---|---|
| 主轴方向 | 从上到下(垂直) | 从左到右(水平) |
| 主轴对齐属性 | justifyContent |
justifyContent |
| 交叉轴对齐属性 | alignItems(HorizontalAlign) |
alignItems(VerticalAlign) |
| 弹性权重 | layoutWeight |
layoutWeight |
| 子组件间距 | space |
space |
这种对称设计使得开发者可以轻松地在纵向和横向布局之间切换,只需将 Column 替换为 Row 并调整对齐方向即可。
4.3 子组件的高度策略
Column 的子组件高度由以下因素决定,按优先级从高到低:
- 显式 height:直接指定数值或百分比
- layoutWeight:弹性权重,占据剩余空间
- 内容自适应:由子组件内部内容决定
- 父容器约束:继承或受父容器限制
理解这四种策略的交互,是掌握 Column 布局的关键。
4.4 尺寸单位体系
ArkTS 支持多种尺寸单位,理解它们的区别对布局至关重要:
| 单位 | 说明 | 换算关系 |
|---|---|---|
vp |
虚拟像素,自适应屏幕密度 | 1vp ≈ 1px(160dpi 屏幕) |
px |
物理像素,与设备分辨率相关 | 不推荐直接使用 |
% |
百分比,相对于父容器同轴尺寸 | 与 CSS 百分比一致 |
fp |
字体像素,跟随系统字体缩放 | 用于字体大小 |
最佳实践: 布局尺寸优先使用 vp 和 %,字体大小使用 fp,避免直接使用 px。
4.5 Column 嵌套的注意事项
Column 可以无限嵌套,但过度嵌套会导致性能下降和布局难以调试。常见的嵌套场景包括:
// ✅ 合理的嵌套:每一层有明确的布局职责
Column() { // 页面根容器
Column() { // 卡片容器
// 卡片内容
}
.borderRadius(12)
.backgroundColor('#FFFFFF')
.padding(16);
Column() { // 另一个卡片容器
// 卡片内容
}
.borderRadius(12)
.backgroundColor('#FFFFFF')
.padding(16);
}
// ❌ 不必要的嵌套:多余的层级
Column() {
Column() {
Column() {
Text('内容'); // 这个 Text 可以直接放在最外层 Column 中
}
}
}
经验法则: 嵌套深度不超过 5 层。如果超过 5 层,考虑将部分内容提取为独立的 @Builder 函数或自定义组件。`
五、核心实战一:Column + layoutWeight 弹性自适应布局
5.1 从 Expanded 到 layoutWeight 的演进
在 HarmonyOS 3.x / API 9 时代,ArkTS 提供了 Expanded 组件作为弹性容器:
// ❌ 旧 API(API 9~22)—— 已废弃
Column() {
Expanded() {
Text('弹性内容')
}
.layoutWeight(1);
}
但在 HarmonyOS NEXT API 23+ 中,Expanded 组件已被移除。取而代之的是直接将 layoutWeight 属性设置在任何子组件上。这一变化简化了组件树层级,提高了布局性能:
// ✅ 新 API(API 23+ 推荐)
Column() {
Text('弹性内容')
.layoutWeight(1); // 直接在子组件上设置权重
}
5.2 layoutWeight 的数学原理
layoutWeight 的本质是弹性增长因子(flex-grow)。当父容器在主轴方向上有剩余空间时,子组件按各自 layoutWeight 值的比例分配这些空间。
计算公式:
子组件分配空间 = 父容器剩余空间 × (该子组件的 layoutWeight / 所有子组件的 layoutWeight 之和)
如果一个子组件设置了 layoutWeight 而其他子组件没有,它将独占全部剩余空间。
5.3 完整示例:多区域弹性布局
以下代码来自 ColumnExpandedDemo.ets,展示了五种不同的弹性布局模式:
/*
* HarmonyOS NEXT (API 24) Column + layoutWeight 弹性自适应布局
*
* 布局要点:
* 1. layoutWeight 直接在子组件上设置,无需 Expanded 包装
* 2. 固定高度 + 弹性组件混合使用,layoutWeight 只占据剩余空间
* 3. 多个弹性组件按权重比例瓜分空间
*/
import router from '@ohos.router';
@Entry
@Component
struct ColumnExpandedDemo {
build() {
Column() {
// ─── ① 顶部标题区(固定高度 80vp) ────────────────
Column() {
Text('Column + layoutWeight 自适应布局')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF');
Text('子组件按比例撑满剩余空间')
.fontSize(14)
.fontColor('#D0E8FF')
.margin({ top: 6 });
}
.width('100%')
.height(80) // 固定高度,不参与弹性分配
.backgroundColor('#007AFF')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center);
// ─── ② Row 内按 layoutWeight 横向比例分配 ────────
Row()
.layoutWeight(1) // 占 1 份剩余纵向空间
.width('100%')
{
// 蓝色区域:layoutWeight(1) → 占 1/3
Column() {
Text('Weight 1')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF');
Text('1 / 3 剩余空间')
.fontSize(13)
.fontColor('#D0E8FF')
.margin({ top: 4 });
}
.layoutWeight(1)
.width('100%').height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('#3498DB');
// 紫色区域:layoutWeight(2) → 占 2/3
Column() {
Text('Weight 2')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF');
Text('2 / 3 剩余空间')
.fontSize(13)
.fontColor('#E8D0FF')
.margin({ top: 4 });
}
.layoutWeight(2)
.width('100%').height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('#8E44AD');
};
// ─── ③ 固定高度 + 弹性组件混合 ────────────────────
Column()
.layoutWeight(1)
.width('100%')
.backgroundColor('#ECF0F1')
{
// 固定高度的文字说明
Text('固定高度 + layoutWeight 混合')
.fontSize(15)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
.width('100%')
.textAlign(TextAlign.Center)
.height(36);
// 弹性部分:Row 内三个子项按 1:1:2 比例分配
Row()
.layoutWeight(1)
.width('100%').height('100%')
{
Column() {
Text('A').fontSize(28).fontWeight(FontWeight.Bold).fontColor('#FFFFFF');
Text('layoutWeight 1').fontSize(12).fontColor('#FFFFFF');
}
.layoutWeight(1).width('100%').height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('#E67E22');
Column() {
Text('B').fontSize(28).fontWeight(FontWeight.Bold).fontColor('#FFFFFF');
Text('layoutWeight 1').fontSize(12).fontColor('#FFFFFF');
}
.layoutWeight(1).width('100%').height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('#2ECC71');
Column() {
Text('C').fontSize(28).fontWeight(FontWeight.Bold).fontColor('#FFFFFF');
Text('layoutWeight 2').fontSize(12).fontColor('#FFFFFF');
}
.layoutWeight(2).width('100%').height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('#1ABC9C');
};
};
// ─── ④ 单个 layoutWeight 撑满全部剩余空间 ──────────
Column() {
Text('单个 layoutWeight 撑满')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#FFFFFF');
Text('只要一个组件设置 layoutWeight,它就会占据全部剩余空间')
.fontSize(12)
.fontColor('#FFD0D0')
.margin({ top: 4 });
}
.layoutWeight(1) // 独占剩余空间
.width('100%').height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('#E74C3C');
// ─── ⑤ 底部操作栏(固定高度 56vp) ──────────────────
Row() {
Button('← 返回')
.fontSize(15).fontColor('#FFFFFF')
.backgroundColor('#555555')
.borderRadius(20).height(36)
.onClick(() => { router.back(); });
Blank().layoutWeight(1); // 弹性空白占位
Text('Column + layoutWeight')
.fontSize(13).fontColor('#999999');
}
.width('100%').height(56) // 固定高度
.padding({ left: 16, right: 16 })
.backgroundColor('#F8F8F8');
}
.width('100%').height('100%')
.backgroundColor('#FFFFFF');
}
}
5.4 运行效果分析
当页面加载后,屏幕被划分为五个纵向区域:
- 蓝色标题栏(80vp):固定高度,不参与弹性计算
- 蓝紫横向分栏(弹性):Row 的
layoutWeight(1)使它在纵向上占 1 份;内部蓝:紫 = 1:2 - 三色横向分栏(弹性):固定文字(36vp)+ 弹性 Row,A:B:C = 1:1:2
- 红色区域(弹性):单个
layoutWeight(1),撑满剩余空间 - 底部栏(56vp):固定高度,不参与弹性计算
关键观察: 无任何 Expanded 组件,所有弹性行为通过 layoutWeight 直接实现。
5.5 常见陷阱与最佳实践
陷阱一:忘记设置父容器弹性
// ❌ 错误:子组件设置了 layoutWeight,但父容器没有弹性空间可供分配
Column() {
Text('内容').layoutWeight(1);
}
.width('100%')
.height('100%'); // 父容器高度是 100%,但 Column 本身没有弹性 → 子组件撑满 Column
// ✅ 正确:父容器本身也参与弹性链
Column() {
Column() {
Text('内容').layoutWeight(1);
}
.layoutWeight(1); // 父容器也要弹性
}
.width('100%').height('100%');
陷阱二:layoutWeight 与固定高度混用时的顺序
// ✅ 正确:固定高度组件在前,弹性组件在后
Column() {
Text('固定标题').height(50); // 先分配 50vp
Text('弹性内容').layoutWeight(1); // 再占据剩余空间
}
陷阱三:height(‘100%’) 与 layoutWeight 同时使用
在 ArkTS 中,layoutWeight 的优先级高于 height('100%')。当同时设置时,layoutWeight 生效,height('100%') 被忽略。因此不需要在弹性组件上同时设置 height('100%'),但写上也无害。
5.6 layoutWeight 的实际应用场景
场景一:表单页面
表单通常包含多个输入区域,每个区域的高度应随屏幕尺寸自适应:
Column() {
// 顶部标题
Text('用户注册').height(60);
// 三个输入区域等分剩余空间
Column() { /* 姓名输入 */ }.layoutWeight(1).width('100%');
Column() { /* 邮箱输入 */ }.layoutWeight(1).width('100%');
Column() { /* 密码输入 */ }.layoutWeight(1).width('100%');
// 底部提交按钮
Button('提交').height(48);
}
.width('100%').height('100%');
场景二:仪表盘卡片
仪表盘页面需要多个卡片按比例分配屏幕空间,不同卡片的重要性不同,通过 layoutWeight 控制占比:
Column() {
// 数据概览卡片(占 2 份)
CardView('今日数据', '...')
.layoutWeight(2)
.width('100%');
// 趋势图表卡片(占 3 份,更大)
CardView('趋势分析', '...')
.layoutWeight(3)
.width('100%');
// 通知列表卡片(占 1 份,最小)
CardView('系统通知', '...')
.layoutWeight(1)
.width('100%');
}
.width('100%').height('100%');
场景三:聊天界面
聊天界面中,消息列表应占据大部分空间,输入框固定在底部:
Column() {
// 聊天标题栏
TitleBar().height(56);
// 消息列表:撑满全部剩余空间
MessageList().layoutWeight(1).width('100%');
// 输入区域:固定高度
InputBar().height(60);
}
.width('100%').height('100%');
六、核心实战二:Column + Divider 分割线布局
6.1 Divider 组件概述
Divider 是鸿蒙系统提供的分割线组件,用于在列表项之间提供视觉分隔。其核心属性:
Divider()
.strokeWidth(1) // 线宽,默认 1vp
.color('#E0E0E0') // 线条颜色,默认 #E0E0E0
.lineCap(LineCapStyle.Round) // 端点样式:Butt / Round / Square
.margin({ top: 8, bottom: 8 }) // 外间距
.dashWidth(6) // 虚线每段长度(设置后变为虚线)
.dashGap(4); // 虚线间隔
6.2 四种 Divider 样式实战
以下代码来自 ColumnDividerDemo.ets,展示了四种常用的分割线场景:
import router from '@ohos.router';
@Entry
@Component
struct ColumnDividerDemo {
build() {
Column() {
// ─── 标题区 ──────────────────────────────────────
Column() {
Text('Column + Divider 分割线布局')
.fontSize(22).fontWeight(FontWeight.Bold).fontColor('#FFFFFF');
Text('列表项间分隔线的多种用法')
.fontSize(14).fontColor('#D0E8FF').margin({ top: 6 });
}
.width('100%').height(80)
.backgroundColor('#2C3E50')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center);
// ─── 示例一:标准实线分割线 ──────────────────────
Column() {
Text('▶ 示例一:标准实线分割线')
.fontSize(16).fontWeight(FontWeight.Medium)
.fontColor('#2C3E50').width('100%').margin({ bottom: 12 });
this.listItem('设置', '系统偏好与账户管理', '#007AFF');
Divider().strokeWidth(1).color('#E0E0E0'); // ← 标准实线
this.listItem('相册', '照片与视频管理', '#E67E22');
Divider().strokeWidth(1).color('#E0E0E0');
this.listItem('音乐', '音频播放与下载', '#2ECC71');
}
.width('100%').padding(16).backgroundColor('#FFFFFF');
// ─── 示例二:带缩进的分割线 ──────────────────────
Column() {
Text('▶ 示例二:带缩进的分割线')
.fontSize(16).fontWeight(FontWeight.Medium)
.fontColor('#2C3E50').width('100%').margin({ bottom: 12 });
this.listItem('无线网络', 'Wi-Fi 6 / 5G 设置', '#3498DB');
Divider()
.strokeWidth(1).color('#E0E0E0')
.margin({ left: 16, right: 16 }); // ← 左右缩进 16vp
this.listItem('蓝牙', '连接耳机与穿戴设备', '#9B59B6');
Divider()
.strokeWidth(1).color('#E0E0E0')
.margin({ left: 16, right: 16 });
this.listItem('蜂窝网络', 'SIM 卡与数据流量', '#E74C3C');
}
.width('100%').padding(16).backgroundColor('#FFFFFF')
.margin({ top: 1 });
// ─── 示例三:虚线分割线 ──────────────────────────
Column() {
Text('▶ 示例三:虚线分割线(个性化)')
.fontSize(16).fontWeight(FontWeight.Medium)
.fontColor('#2C3E50').width('100%').margin({ bottom: 12 });
this.listItem('通知', '消息推送与提醒', '#1ABC9C');
Divider()
.strokeWidth(2) // 较粗
.color('#1ABC9C') // 主题色
.margin({ top: 8, bottom: 8 })
.dashWidth(6) // ← 虚线:每段 6vp
.dashGap(4); // ← 虚线:间隔 4vp
this.listItem('隐私', '权限管理与安全', '#E67E22');
Divider()
.strokeWidth(2).color('#E67E22')
.margin({ top: 8, bottom: 8 })
.dashWidth(6).dashGap(4);
this.listItem('辅助功能', '视觉与交互辅助', '#8E44AD');
}
.width('100%').padding(16).backgroundColor('#FFFFFF')
.margin({ top: 1 });
// ─── 示例四:Column space + Divider 组合 ──────────
Column({ space: 8 }) { // ← space 统一间距
Text('▶ 示例四:space + Divider 组合')
.fontSize(16).fontWeight(FontWeight.Medium)
.fontColor('#2C3E50').width('100%').margin({ bottom: 4 });
this.listItem('语言', 'English / 简体中文', '#34495E');
Divider().strokeWidth(1).color('#E0E0E0');
this.listItem('地区', '中国大陆 / 香港', '#16A085');
Divider().strokeWidth(1).color('#E0E0E0');
this.listItem('键盘', '输入法与快捷键', '#D35400');
Divider().strokeWidth(1).color('#E0E0E0');
this.listItem('日期与时间', '24 小时制 / 时区', '#2980B9');
}
.width('100%').padding(16).backgroundColor('#FFFFFF')
.margin({ top: 1 });
// ─── 底部操作栏 ──────────────────────────────────
Row() {
Button('← 返回')
.fontSize(15).fontColor('#FFFFFF')
.backgroundColor('#555555').borderRadius(20).height(36)
.onClick(() => { router.back(); });
Blank().layoutWeight(1);
Text('Column + Divider').fontSize(13).fontColor('#999999');
}
.width('100%').height(56)
.padding({ left: 16, right: 16 })
.backgroundColor('#F8F8F8');
}
.width('100%').height('100%')
.backgroundColor('#F0F0F0');
}
/**
* 封装列表项组件,避免重复代码
* @param title 主标题
* @param subtitle 副标题
* @param accentColor 左侧装饰色块
*/
@Builder
listItem(title: string, subtitle: string, accentColor: string) {
Row() {
// 左侧装饰色块
Column()
.width(4).height('70%')
.backgroundColor(accentColor)
.borderRadius(2);
// 文字区域
Column() {
Text(title).fontSize(16).fontWeight(FontWeight.Medium).fontColor('#333333');
Text(subtitle).fontSize(13).fontColor('#999999').margin({ top: 2 });
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 });
Blank().layoutWeight(1); // 弹性空白
Text('>').fontSize(16).fontColor('#CCCCCC'); // 右箭头
}
.width('100%').height(56)
.alignItems(VerticalAlign.Center);
}
}
6.3 Divider 样式对比
| 样式 | 关键属性 | 适用场景 |
|---|---|---|
| 标准实线 | 默认值 | 通用列表项分隔 |
| 缩进实线 | margin({ left: X, right: X }) |
设置页、iOS 风格列表 |
| 虚线 | dashWidth(6).dashGap(4) |
弱化分隔、分组提示 |
| 自定义粗细/颜色 | strokeWidth(2).color('#1ABC9C') |
主题化、品牌色 |
6.4 space 与 Divider 的协同
Column 的 space 属性和 Divider 组件解决了不同维度的问题:
space:控制子组件之间的间距(数值),不产生视觉元素Divider:提供视觉分割线(图形),本身不产生间距
最佳实践: 两者结合使用。
// ✅ 推荐:space 控制间距,Divider 提供视觉分割
Column({ space: 8 }) {
this.listItem('分类一', '描述', '#007AFF');
Divider().strokeWidth(1).color('#E0E0E0');
this.listItem('分类二', '描述', '#E67E22');
}
如果只使用 Divider 而不设置 space,分割线与列表项将紧贴在一起,视觉上拥挤。如果只使用 space 而不使用 Divider,列表项之间只有空白,缺少边界感。
七、@Builder 构建函数:复用 UI 的利器
7.1 @Builder 的基本用法
在 ColumnDividerDemo.ets 中,我们通过 @Builder 封装了 listItem 组件,避免了四个示例中重复编写相同的列表项布局代码:
@Builder
listItem(title: string, subtitle: string, accentColor: string) {
Row() {
// 左侧色块 + 文字 + 弹性空白 + 右箭头
}
.width('100%').height(56)
.alignItems(VerticalAlign.Center);
}
7.2 @Builder 的特性
| 特性 | 说明 |
|---|---|
| 参数化 | 支持任意数量参数,类型安全 |
| 无返回值 | 直接构建 UI 节点,不返回组件 |
| 调用方式 | 使用 this.方法名(参数) 调用 |
| 作用域 | 绑定到所属组件实例,可访问组件状态 |
7.3 何时使用 @Builder
- 同一布局模式在页面中出现 2 次以上
- 列表项、卡片、表单行等重复性 UI 单元
- 需要参数化控制颜色、文字、图标等
八、布局性能优化建议
8.1 减少组件树深度
// ❌ 不推荐:多余的嵌套层级
Column() {
Column() {
Column() {
Text('内容');
}
}
}
// ✅ 推荐:扁平化布局
Column() {
Text('内容');
}
8.2 合理使用 layoutWeight
layoutWeight 的布局计算发生在测量阶段,过多的弹性组件会增加布局计算量。对于只需要均分空间的场景,优先使用 layoutWeight 而非嵌套 Flex。
8.3 Divider 的渲染开销
Divider 是一个轻量级组件,渲染开销极低。但若列表中包含大量 Divider(数百个),建议使用 LazyForEach + 自定义分割线(通过 Column 的 border 属性模拟)以优化性能。
九、从零搭建项目的完整流程
9.1 创建项目
- 打开 DevEco Studio,选择 “Create Project”
- 选择 “Empty Ability” 模板
- 填写项目名称和包名
- 选择 “Stage Model” 和 “ArkTS” 语言
- 确认 SDK 版本为 API 24(HarmonyOS NEXT 6.1.1)
9.2 添加页面
- 在
entry/src/main/ets/pages/目录下创建.ets文件 - 在文件中添加
@Entry @Component装饰器 - 在
main_pages.json中注册新页面
9.3 页面跳转
import router from '@ohos.router';
// 跳转到目标页面
router.pushUrl({
url: 'pages/ColumnDividerDemo'
});
// 返回上一页
router.back();
9.4 编译运行
# 调试模式编译
hvigorw assembleHap --mode module -p product=default
# 或使用 DevEco Studio 的 Run 按钮直接运行
十、常见问题 FAQ
Q1:layoutWeight 和 height: ‘100%’ 有什么区别?
layoutWeight 是弹性分配,height('100%') 是百分比填充。前者在父容器有剩余空间时才生效,后者始终填充父容器的 100% 高度。当两者同时存在时,layoutWeight 优先。
Q2:为什么我的 Divider 不显示?
最常见的原因是 Divider 没有设置宽度或父容器没有宽度约束。Divider 默认宽度为父容器宽度的 100%,如果父容器宽度为 0 或未设置,Divider 将不可见。解决方案:确保父容器设置了 width('100%')。
Q3:@Builder 中的方法可以调用组件状态吗?
可以。@Builder 方法绑定到组件实例,可以访问 @State、@Prop 等装饰器修饰的变量。但 @Builder 方法内部不能使用 @State 装饰器。
Q4:多个 layoutWeight 组件的权重之和必须为 1 吗?
不需要。权重值可以是任意正数,比例计算使用相对值。例如权重 2 和 4 等价于 1 和 2。
Q5:可以在 Row 中使用 layoutWeight 吗?
可以。layoutWeight 在 Row 中沿水平方向分配空间,在 Column 中沿垂直方向分配空间,行为完全一致。
十一、总结
本文通过一个完整的 HarmonyOS NEXT 示例项目,系统性地讲解了 ArkTS 中 Column 布局的两大核心能力:
1. layoutWeight 弹性布局
Expanded组件已在 API 23+ 中移除,应直接在子组件上使用layoutWeight- 多个弹性组件按权重比例瓜分父容器的剩余空间
- 固定高度组件与弹性组件可以混合使用
2. Divider 分割线布局
- 支持标准实线、缩进实线、虚线、自定义样式四种变体
space控制间距,Divider提供视觉分割,两者协同使用效果最佳@Builder封装列表项组件,显著提升代码复用性
3. 布局设计原则
- 优先使用 ArkTS 原生布局容器,避免过度嵌套
- 理解
layoutWeight的计算公式,合理分配弹性权重 - 区分
space与Divider的职责,组合使用以达到最佳视觉体验
HarmonyOS NEXT 的 ArkTS 布局体系经过 API 23+ 的演进,已经形成了成熟、简洁、高效的声明式 UI 框架。掌握 Column + layoutWeight + Divider 的组合,足以应对 90% 以上的纵向布局需求。希望本文能为正在学习鸿蒙原生开发的读者提供切实的帮助。
本文作者: AtomCode(deepseek-v4-flash)
编写日期: 2026 年 7 月 20 日
SDK 版本: HarmonyOS NEXT 6.1.1(API 24)
开发工具: DevEco Studio
项目源码: 本文所有代码均来自MyApplication3工程,可直接编译运行
更多推荐


所有评论(0)