鸿蒙原生 ArkTS 布局精讲:RelativeContainer 实现居中加载动画
鸿蒙原生 ArkTS 布局精讲:RelativeContainer 实现居中加载动画
基于 HarmonyOS NEXT API 24(SDK 7.0+),使用 ArkTS + RelativeContainer + LoadingProgress,构建一个优雅的全屏居中加载动画页面。



一、前言
HarmonyOS NEXT 彻底剥离 Android 内核走向全栈自研,开发者必须掌握纯正的鸿蒙原生开发技术——ArkTS 语言和 ArkUI 声明式 UI 框架。本文将围绕 RelativeContainer + LoadingProgress,深入讲解如何在鸿蒙 NEXT 下实现全屏居中加载动画。
二、环境准备与 API 版本说明
| 项目 | 说明 |
|---|---|
| IDE | DevEco Studio 5.0+ |
| SDK | HarmonyOS SDK 7.0.0 (API 24) |
| 语言 | ArkTS |
| 框架 | ArkUI(声明式 UI) |
API 24 是鸿蒙生态的重要里程碑,相比 API 23 在类型安全、组件库和运行时性能上均有提升。
三、核心技术概览
3.1 RelativeContainer —— 相对定位容器
RelativeContainer 是 ArkUI 提供的相对定位布局容器,子组件通过 alignRules 指定自身相对于父容器或其他兄弟组件的摆放位置。
与 Row / Column 线性布局不同,RelativeContainer 不依赖代码书写顺序,通过显式声明锚点实现更自由精确的定位。
核心定位语法:
alignRules({
top: { anchor: '锚点组件ID', align: VerticalAlign.Top },
center: { anchor: '锚点组件ID', align: VerticalAlign.Center },
bottom: { anchor: '锚点组件ID', align: VerticalAlign.Bottom },
left: { anchor: '锚点组件ID', align: HorizontalAlign.Left },
middle: { anchor: '锚点组件ID', align: HorizontalAlign.Center },
right: { anchor: '锚点组件ID', align: HorizontalAlign.Right },
})
每个条目包含两个关键字段:
| 字段 | 类型 | 说明 |
|---|---|---|
anchor |
string | 锚点组件的 id,__container__ 表示父容器 |
align |
enum | 对齐方式,使用 VerticalAlign / HorizontalAlign |
3.2 __container__ —— 特殊锚点名
__container__ 是保留锚点名,代表父容器本身。当组件使用 __container__ 作为锚点,将相对于 RelativeContainer 的边界进行定位。例如:
center: { anchor: '__container__', align: VerticalAlign.Center } // 垂直居中
middle: { anchor: '__container__', align: HorizontalAlign.Center } // 水平居中
两者组合即实现正中央居中效果。
3.3 LoadingProgress —— 原生加载动画
LoadingProgress 是 ArkUI 内置的轻量加载动画组件:
- 开箱即用:无需额外引入动画资源,自带旋转动效
- 可定制颜色:通过
.color()改变图标颜色 - 轻量高效:GPU 渲染,性能开销极小
- 自动动画:挂载后自动旋转,无需手动触发
基本用法:
LoadingProgress()
.width(60)
.height(60)
.color(Color.White)
四、需求分析与设计思路
4.1 需求
- 全屏正中央显示 LoadingProgress 旋转加载图标
- 加载图标下方显示动态提示文字
- 提示文字下方显示实时进度百分比(0%→100% 循环)
- 页面底部显示版本版权信息
- 渐变色背景,视觉效果优雅
4.2 布局设计
┌─────────────────────────────────────────┐
│ RelativeContainer (100%×100%) │
│ ┌─ linearGradient 背景 ───────────────┐│
│ ││
│ ┌──────────┐ ││
│ │Loading │ ← 组件① ││
│ │Progress │ anchor: ││
│ │ (60×60) │ __container__ ││
│ └────┬─────┘ 正中央 ││
│ │ ││
│ ┌───────┴────────┐ ││
│ │正在加载... │ ← 组件② ││
│ │ anchor: loadingSpinner ││
│ │ top → bottom │ ││
│ └───────┬────────┘ ││
│ │ ││
│ ┌───────┴────────┐ ││
│ │ 56% │ ← 组件③ ││
│ └────────────────┘ ││
│ ││
│ ┌──────────────────────────────────┐ ││
│ │ RelativeContainer + LoadingProgress│ ││
│ │ 示例 ← 组件④ bottom → __container__│ ││
│ └──────────────────────────────────┘ ││
└─────────────────────────────────────────┘
4.3 组件关系
| 组件 | ID | 锚点 | 效果 |
|---|---|---|---|
| LoadingProgress | loadingSpinner |
__container__ center + middle |
正中央 |
| 提示文字 | loadingLabel |
loadingSpinner top→bottom |
图标正下方 |
| 百分比文字 | progressPercent |
loadingSpinner top→bottom |
图标下方 |
| 底部文字 | footerHint |
__container__ bottom + middle |
底部居中 |
五、完整代码
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct Index {
@State loadingProgress: number = 0;
@State loadingText: string = '正在加载...';
private timerId: number = -1;
aboutToAppear(): void {
this.timerId = setInterval(() => {
if (this.loadingProgress < 100) {
this.loadingProgress++;
} else {
this.loadingProgress = 0;
}
if (this.loadingProgress < 30) {
this.loadingText = '正在加载...';
} else if (this.loadingProgress < 60) {
this.loadingText = '加载中,请稍候';
} else if (this.loadingProgress < 90) {
this.loadingText = '即将完成...';
} else {
this.loadingText = '马上就好...';
}
}, 80);
}
aboutToDisappear(): void {
clearInterval(this.timerId);
}
build() {
RelativeContainer() {
// 组件① 加载图标——锚定父容器正中央
LoadingProgress()
.id('loadingSpinner')
.width(60).height(60).color(Color.White)
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.shadow({ radius: 12, color: 'rgba(0,0,0,0.15)', offsetX: 0, offsetY: 4 })
// 组件② 提示文字——锚定到 loadingSpinner 底部
Text(this.loadingText)
.id('loadingLabel')
.fontSize(16)
.fontColor('rgba(255,255,255,0.90)')
.fontWeight(FontWeight.Medium)
.textAlign(TextAlign.Center)
.alignRules({
top: { anchor: 'loadingSpinner', align: VerticalAlign.Bottom },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.margin({ top: 16 })
// 组件③ 百分比文字
Text(this.loadingProgress + '%')
.id('progressPercent')
.fontSize(14)
.fontColor('rgba(255,255,255,0.60)')
.textAlign(TextAlign.Center)
.alignRules({
top: { anchor: 'loadingSpinner', align: VerticalAlign.Bottom },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.margin({ top: 48 })
// 组件④ 底部提示——锚定到父容器底部
Text('RelativeContainer + LoadingProgress 示例')
.id('footerHint')
.fontSize(12)
.fontColor('rgba(255,255,255,0.35)')
.textAlign(TextAlign.Center)
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.margin({ bottom: 32 })
}
.width('100%').height('100%')
.linearGradient({
direction: GradientDirection.Bottom,
colors: [['#667eea', 0], ['#764ba2', 1]]
})
}
}
六、代码深度解析
6.1 @Entry @Component 装饰器
@Entry // 标记为页面入口,每个页面唯一
@Component // 声明为 ArkUI 自定义组件
struct Index { }
API 24 中 @Component 支持更严格的类型检查和更丰富的生命周期钩子。
6.2 @State 响应式状态
@State loadingProgress: number = 0;
@State loadingText: string = '正在加载...';
@State 是 ArkTS 响应式编程的核心——变量变化时框架自动重绘依赖该变量的 UI。loadingProgress 每 80ms 递增驱动百分比更新;loadingText 随进度阶段变化,让用户感知加载进程。
6.3 生命周期管理
| 回调 | 触发时机 | 用途 |
|---|---|---|
aboutToAppear |
组件即将挂载到视图树 | 启动定时器、初始化数据 |
aboutToDisappear |
组件即将从视图树移除 | 清除定时器、释放资源 |
关键:必须在 aboutToDisappear 中清除 setInterval,否则页面退出后定时器仍在运行,导致内存泄漏。
6.4 alignRules 定位详解
场景一:相对于父容器居中
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
执行逻辑:
center→ 组件的垂直中心线anchor: '__container__'→ 锚定到父容器align: VerticalAlign.Center→ 与锚点垂直中心线对齐- 结果:组件垂直中心线与父容器垂直中心线重合 → 垂直居中
- 同理
middle实现水平居中
场景二:相对于兄弟组件底部对齐
.alignRules({
top: { anchor: 'loadingSpinner', align: VerticalAlign.Bottom },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
执行逻辑:
top→ 组件的上边界anchor: 'loadingSpinner'→ 锚定到 loadingSpinneralign: VerticalAlign.Bottom→ 与锚点组件的下边界对齐- 结果:组件上边界紧贴在 loadingSpinner 下边界上
- 通过
.margin({ top: 16 })增加 16vp 间距
两个场景展示了 RelativeContainer 的一大优势:一个组件可以同时相对于不同锚点定位不同方向——垂直方向跟兄弟组件,水平方向跟父容器。
6.5 渐变色背景
.linearGradient({
direction: GradientDirection.Bottom,
colors: [
['#667eea', 0], // 起始色
['#764ba2', 1] // 结束色
]
})
API 24 的 GradientDirection 枚举包含:Left / Right / Top / Bottom(基本方向)和 LeftTop / LeftBottom / RightTop / RightBottom(对角线方向)。
注意:
BottomEnd、BottomStart、TopEnd、TopStart在 API 23 中存在,但在 API 24 中已移除。迁移时请改用RightBottom或Bottom。
七、RelativeContainer 对比其他布局
| 布局方式 | 居中复杂度 | 兄弟定位 | 推荐场景 |
|---|---|---|---|
| RelativeContainer | 低(alignRules) | 强 | 居中、锚定、叠加布局 |
| Row + justifyContent | 低 | 弱 | 水平排列 |
| Column + alignItems | 低 | 弱 | 垂直排列 |
| Stack + alignContent | 中 | 中 | 叠加布局 |
对于"居中加载动画"场景,RelativeContainer 的独特优势在于:
- 声明式定位:不依赖代码书写顺序,每个组件独立声明位置
- 组件间锚定:提示文字轻松跟随加载图标
- 混合锚定:同一组件垂直方向跟兄弟组件、水平方向跟父容器
- 容器即锚点:无需额外嵌套,直接
__container__锚定
如果使用 Column 实现:
Column() {
LoadingProgress().width(60).height(60).color(Color.White)
Text('正在加载...').fontSize(16).margin({ top: 16 })
Text('56%').fontSize(14).margin({ top: 8 })
}
.width('100%').height('100%').justifyContent(FlexAlign.Center)
这种方法无法实现"底部信息固定在页面底部"——文字会随 Column 整体居中而非固定在底部。RelativeContainer 则能轻松实现"部分居中、部分固定底部"的混合布局。
八、LoadingProgress 属性与最佳实践
| 属性 | 类型 | 说明 |
|---|---|---|
width / height |
Length | 图标尺寸,全屏加载建议 50~70vp |
color |
ResourceColor | 支持 Color 枚举、十六进制、rgba、资源引用 |
visibility |
Visibility | 可见性控制 |
enabled |
boolean | 是否启用 |
颜色示例:
// 四种写法均有效
.color(Color.White)
.color('#FFFFFF')
.color('rgba(255, 255, 255, 0.85)')
.color($r('app.color.loading_color'))
大小建议:
- 全屏加载页:50~70vp
- 局部加载(下拉刷新):24~36vp
- 按钮内加载:16~20vp
九、API 24 注意事项
9.1 模块导入路径
API 24 统一使用 @kit.* 格式:
| 功能 | 旧路径(API ≤ 23) | 新路径(API 24) |
|---|---|---|
| 基础服务 | @ohos.basicServicesKit |
@kit.BasicServicesKit |
| 网络请求 | @ohos.net.http |
@kit.NetworkKit |
| 数据存储 | @ohos.data.preferences |
@kit.DataKit |
9.2 GradientDirection 变化
API 24 移除了 BottomEnd 等方向变体,编译时若遇此错误,直接替换即可:
- direction: GradientDirection.BottomEnd,
+ direction: GradientDirection.Bottom,
9.3 定时器生命周期
setInterval / clearInterval 在 API 24 中行为不变,但务必配对使用,避免页面销毁后定时器持续执行。
十、扩展实践
10.1 结合显隐控制
实际业务中通常需要控制加载页的显隐:
@State isLoading: boolean = true;
build() {
Stack() {
YourMainContent()
if (this.isLoading) {
RelativeContainer() { /* 加载动画代码 */ }
.width('100%').height('100%')
}
}
}
10.2 配合页面路由
import { router } from '@kit.ArkUI';
this.isLoading = true;
setTimeout(() => {
router.pushUrl({ url: 'pages/TargetPage' });
}, 2000);
十一、常见问题
Q1:alignRules 不生效?
A:最常见的原因是忘记设置 .id()。RelativeContainer 通过 ID 识别锚点组件。
Q2:加载动画卡顿?
A:LoadingProgress 使用 GPU 渲染,正常情况下非常流畅。检查是否有大量 UI 更新在同一帧触发。
Q3:编译报错 “Child component of RelativeContainer must have a unique ID”?
A:所有子组件必须设置唯一 ID,且不可重复。
Q4:API 24 报错 “Property ‘BottomEnd’ does not exist”?
A:改用 GradientDirection.Bottom 或 GradientDirection.RightBottom。
十二、总结
本文通过一个完整的全屏居中加载动画示例,讲解了 HarmonyOS NEXT(API 24)中 RelativeContainer 和 LoadingProgress 的联合使用。
核心要点:
- RelativeContainer 通过
alignRules+__container__实现精确居中 - LoadingProgress 是开箱即用的加载动画,配合
@State实现动态进度 - alignRules 支持锚定父容器、锚定兄弟组件、混合锚定
- 生命周期管理:
aboutToAppear启动、aboutToDisappear清理 - API 24:导入路径用
@kit.*,注意GradientDirection枚举变化
RelativeContainer 是鸿蒙布局体系中的"瑞士军刀"——不一定写代码最快,但一定最灵活、最可控。掌握好它,鸿蒙应用的各种复杂布局都能游刃有余。
完整项目代码可在 DevEco Studio 中打开运行,直观感受 RelativeContainer + LoadingProgress 的布局效果。
更多推荐



所有评论(0)