【共创季稿事节】鸿蒙原生 ArkTS 布局实战:Stack + LoadingProgress 实现全屏加载动画覆盖
鸿蒙原生 ArkTS 布局实战:Stack + LoadingProgress 实现全屏加载动画覆盖



一、引言
在移动应用开发中,「加载等待」是一个无法回避的场景——网络请求、数据解析、页面初始化等操作都需要时间。一个设计良好的加载动画不仅能缓解用户的焦虑情绪,还能提升应用的整体品质感。
HarmonyOS NEXT 作为华为全场景智慧生态的操作系统底座,提供了强大的 ArkUI 声明式 UI 框架。其中,Stack 容器与 LoadingProgress 组件的组合,是实现全屏加载动画覆盖最优雅、最原生的方案。
本文将以一个完整的示例项目为主线,深入讲解如何利用 Stack 容器实现「底层内容 + 顶层加载遮罩」的层叠布局,并结合 @State 响应式数据驱动 UI 显隐切换,打造流畅的加载体验。
二、核心概念速览
2.1 Stack 层叠容器
Stack 是 ArkUI 提供的层叠布局容器,其核心特征如下:
| 特性 | 说明 |
|---|---|
| 层叠规则 | 子组件按书写顺序自下而上堆叠,后写的子组件在上层 |
| 对齐方式 | 默认所有子组件从左上角开始定位,可通过 alignContent 修改 |
| 尺寸策略 | 默认自适应内容,可通过 .width() / .height() 显式设定 |
| 适用场景 | 遮罩层、悬浮按钮、徽章、加载覆盖、弹窗等 |
在 ArkTS 中,Stack 的层叠逻辑可以形象地理解为"后入先出"(Last In, First Rendered on Top),即越晚声明的子组件,在视觉上越靠前。
2.2 LoadingProgress 加载指示器
LoadingProgress 是 HarmonyOS 内置的加载动画组件,无需引入任何第三方库即可使用:
| 属性 | 类型 | 说明 |
|---|---|---|
.color(value) |
ResourceColor |
设置加载圈颜色 |
.width(value) |
Length |
设置加载圈宽度(直径) |
.height(value) |
Length |
设置加载圈高度(直径) |
该组件会自动播放旋转动画,开发者只需控制其显示时机即可。
2.3 @State 响应式状态
@State 是 ArkTS 中最基础的状态装饰器:
- 被
@State修饰的变量发生变化时,UI 自动重新渲染 - 适用于组件内部的私有状态
- 配合条件渲染(
if/else),可实现加载层的显隐切换
三、完整代码实现
3.1 项目结构
entry/src/main/ets/pages/
└── Index.ets ← 主页面,包含全部实现
3.2 完整代码
/*
* 全屏加载动画覆盖(Stack + LoadingProgress)
*
* █ 布局要点:
* 1. Stack 容器作为根布局,子组件按「后进先绘制」规则层叠。
* 2. 底层(第一个子组件):主页面内容(如列表、卡片等)。
* 3. 顶层(第二个子组件):全屏半透明遮罩 + LoadingProgress 加载动画。
* 4. 通过 @State isLoading 控制加载层的显示/隐藏,实现自然的加载→内容过渡。
* 5. LoadingProgress 配合 .width()/.height() 可调节加载圈大小。
*/
// ============ 必要的 import 语句 ============
// Stack、LoadingProgress、Text、Column、Row、Divider、Blank、ForEach 等均为
// 鸿蒙 ArkUI 框架内置组件,无需额外 import。
// 如需使用 router 能力可在此引入:
// import { router } from '@kit.ArkUI';
// ============ 页面入口 ============
@Entry
@Component
struct Index {
/* ---------- 状态变量 ---------- */
@State isLoading: boolean = true; // 是否处于加载状态(控制加载层显隐)
@State progressValue: number = 0; // 模拟进度值(0~100)
private progressTimer: number = -1; // 定时器 ID
/* ---------- 生命周期:页面加载完成时自动触发加载动画 ---------- */
aboutToAppear(): void {
// 启动模拟加载任务:每 30ms 增加一次进度
this.progressTimer = setInterval(() => {
if (this.progressValue < 100) {
this.progressValue += 2;
} else {
// 加载完毕 → 关闭加载层,清除定时器
this.isLoading = false;
clearInterval(this.progressTimer);
this.progressTimer = -1;
}
}, 30);
}
/* ---------- 页面卸载时清理定时器 ---------- */
aboutToDisappear(): void {
if (this.progressTimer !== -1) {
clearInterval(this.progressTimer);
this.progressTimer = -1;
}
}
/* ---------- UI 构建 ---------- */
build() {
// ─────────────────────────────────────────────
// 核心:Stack 容器实现层叠效果
// ┌─────────────────────┐
// │ 主页面内容(底层)│ ← 始终存在,被加载层遮挡时不可见
// ├─────────────────────┤
// │ 加载遮罩层(顶层) │ ← isLoading=true 时显示,false 时消失
// └─────────────────────┘
// ─────────────────────────────────────────────
Stack() {
/* ======== 1. 底层:主页面内容 ======== */
Column() {
// 标题区
Text('鸿蒙原生 ArkTS 布局示例')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor('#1A73E8')
.margin({ top: 20, bottom: 8 });
Text('Stack + LoadingProgress 全屏加载覆盖')
.fontSize(14)
.fontColor('#666666')
.margin({ bottom: 20 });
// 分隔线
Divider()
.strokeWidth(1)
.color('#E0E0E0')
.width('90%')
.margin({ bottom: 16 });
// 模拟内容卡片区
ForEach(this.getMockCards(), (item: MockCard, index: number) => {
CardItem({ title: item.title, desc: item.desc, color: item.color })
}, (item: MockCard, index: number) => item.title + index.toString());
// 底部说明
Blank()
.layoutWeight(1);
Text('加载完成后自动进入主页面')
.fontSize(12)
.fontColor('#AAAAAA')
.margin({ bottom: 30 });
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
.padding({ left: 16, right: 16 })
// ⚠️ 关键:底层必须占满 Stack,否则加载层无法全屏覆盖
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]);
/* ======== 2. 顶层:全屏加载遮罩层 ======== */
// 条件渲染:isLoading=true 时显示加载层
if (this.isLoading) {
Column() {
// ── 加载动画核心 ──
LoadingProgress()
.width(48) // 加载圈直径 48vp
.height(48)
.color('#1A73E8') // 蓝色加载圈,与标题色呼应
.margin({ bottom: 20 });
// 加载提示文字
Text('正在加载...')
.fontSize(16)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Medium)
.margin({ bottom: 12 });
// 进度百分比显示
Text(this.progressValue + '%')
.fontSize(13)
.fontColor('rgba(255,255,255,0.8)');
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center) // 主轴居中
.alignItems(HorizontalAlign.Center) // 交叉轴居中
// ⚠️ 关键:半透明遮罩背景,覆盖全屏
.backgroundColor('rgba(0, 0, 0, 0.6)')
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]);
}
}
.width('100%')
.height('100%')
// ⚠️ 关键:Stack 默认子组件从左上角开始排列,
// 每个子组件都设为 100% 宽高即可实现全屏覆盖
}
/* ---------- 模拟数据 ---------- */
getMockCards(): MockCard[] {
return [
{ title: '布局方式:Stack', desc: '层叠容器,子组件按顺序堆叠', color: '#E3F2FD' },
{ title: '组件:LoadingProgress', desc: '系统加载动画指示器,支持颜色/大小定制', color: '#E8F5E9' },
{ title: '状态控制:@State', desc: '响应式数据驱动 UI 显隐切换', color: '#FFF3E0' },
{ title: '动画效果:渐显过渡', desc: '加载完成后遮罩平滑消失', color: '#F3E5F5' },
];
}
}
/* ============ 自定义子组件:卡片 ============ */
@Component
struct CardItem {
@Prop title: string = '';
@Prop desc: string = '';
@Prop color: string = '#FFFFFF';
build() {
Row() {
// 左侧色块
Column()
.width(6)
.height('100%')
.backgroundColor(this.color)
.borderRadius({ topLeft: 8, bottomLeft: 8 });
// 右侧文字
Column() {
Text(this.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
.width('100%');
Text(this.desc)
.fontSize(13)
.fontColor('#888888')
.width('100%')
.margin({ top: 4 });
}
.alignItems(HorizontalAlign.Start)
.padding({ left: 12, right: 12, top: 12, bottom: 12 });
}
.width('100%')
.height(72)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.margin({ bottom: 10 })
// ⚠️ 注意:鸿蒙 ArkTS 的阴影 API 在 API 12+ 中为 .shadow()
.shadow({
radius: 4,
color: 'rgba(0,0,0,0.08)',
offsetX: 0,
offsetY: 2
})
.alignItems(VerticalAlign.Center);
}
}
/* ============ 数据模型 ============ */
interface MockCard {
title: string;
desc: string;
color: string;
}
四、布局原理解析
4.1 层叠结构拆解
本示例的核心布局仅由 一个 Stack 容器 + 两个子组件 构成:
Stack(根容器,宽高 100%)
├── Column(底层:主页面内容) 宽高 100%
│ ├── Text(标题)
│ ├── Divider(分隔线)
│ ├── ForEach → CardItem × 4(卡片列表)
│ └── Text(底部提示)
│
└── Column(顶层:加载遮罩层) 宽高 100%,条件渲染
├── LoadingProgress(加载动画圈)
├── Text("正在加载...")
└── Text(进度百分比)
关键要点:
-
两个子组件均设置
width('100%')和height('100%')——这是实现全屏覆盖的前提条件。如果底层 Column 没有撑满 Stack,顶层遮罩也无法覆盖到边缘区域。 -
顶层使用
if (this.isLoading)条件渲染——当isLoading为true时,遮罩层存在并覆盖在内容之上;当加载完成、isLoading变为false时,遮罩层从组件树中移除,底层内容自然暴露。 -
expandSafeArea扩展安全区域——通过expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])让遮罩层延伸到状态栏和导航栏区域,实现真正的全屏覆盖。
4.2 数据驱动显隐切换
ArkTS 的状态管理机制是本示例能够流畅运行的根本原因:
用户操作 / 网络请求 / 定时器
↓
修改 @State 变量
↓
ArkUI 框架自动检测状态变化
↓
触发组件树的局部更新
↓
加载层移入/移出组件树
↓
底层内容显示/隐藏
整个流程中,开发者只需要关注业务逻辑(修改状态),UI 同步由框架自动完成。
4.3 生命周期管理
在 aboutToAppear() 中启动定时器模拟加载,在 aboutToDisappear() 中清理定时器——这是 ArkTS 标准的生命周期管理方式:
| 生命周期方法 | 调用时机 | 作用 |
|---|---|---|
aboutToAppear() |
组件创建后、build() 前 | 初始化数据、启动异步任务 |
aboutToDisappear() |
组件销毁前 | 清理定时器、取消订阅、释放资源 |
五、常见问题与最佳实践
5.1 为什么选择 Stack 而非 Column / Flex?
- Column / Flex 是线性布局,子组件沿主轴排列,无法实现层叠覆盖效果。
- Stack 允许子组件在 Z 轴方向上堆叠,天然适合遮罩、悬浮层等场景。
- 如果加载层不需要覆盖全屏(如仅覆盖内容区域、保留顶部导航栏),可将 Stack 嵌套在页面内而非作为根容器。
5.2 如何实现加载层的过渡动画?
本示例中加载层的消失是瞬间移除的。如需淡入淡出效果,可使用 ArkUI 的 animateTo 或显式动画:
// 淡出效果示例(在 isLoading 变为 false 前调用)
animateTo({ duration: 300, curve: Curve.EaseInOut }, () => {
this.isLoading = false;
});
5.3 LoadingProgress 的局限性
LoadingProgress仅提供无限旋转的加载动画,不支持进度条模式。- 如果需要圆形进度条(可显示百分比),应使用
Progress组件并设置type: ProgressType.Ring。 - 可以通过
.color()修改颜色以适配品牌色调。
5.4 性能注意事项
- 条件渲染 vs 透明度控制:使用
if条件渲染(本示例)会在隐藏时完全移除节点,内存更优;使用.opacity(0)配合.hitTestBehavior(HitTestBehavior.None)则保留节点但不可交互,适合需要频繁显隐的场合。 - 定时器清理:务必在
aboutToDisappear()中清理定时器,防止页面销毁后定时器仍在执行导致内存泄漏或崩溃。 - 避免过度嵌套:Stack 内部不宜嵌套过多层 Stack,会影响布局性能和可维护性。
六、效果对比:有加载动画 vs 无加载动画
| 场景 | 无加载动画 | 有加载动画(本方案) |
|---|---|---|
| 用户体验 | 页面白屏或卡顿,用户可能误以为闪退 | 视觉连续,用户感知到"正在加载" |
| 感知等待时间 | 感觉漫长(即使实际耗时很短) | 感觉流畅(加载动画分散了注意力) |
| 品牌感知 | 粗糙、未完成 | 精致、专业 |
| 实现成本 | 0 | 极低(约 50 行代码) |
七、扩展思路
7.1 结合网络请求
将 isLoading 的赋值与实际网络请求绑定:
async loadData() {
this.isLoading = true;
try {
const data = await HttpUtil.get('/api/data');
// 处理数据...
} finally {
this.isLoading = false;
}
}
7.2 多阶段加载
使用不同的加载状态展示不同的遮罩内容:
enum LoadState { LOADING, SUCCESS, ERROR }
@State loadState: LoadState = LoadState.LOADING;
// 在 if 中根据 loadState 显示加载中/错误重试/内容
7.3 配合骨架屏
在加载层中展示与内容结构相似的骨架屏占位符(Skeleton),让用户提前感知页面结构,进一步降低等待焦虑。
八、结语
本文通过一个完整的全屏加载动画覆盖示例,展示了 HarmonyOS NEXT 原生 ArkTS 布局中 Stack 层叠容器 与 LoadingProgress 加载指示器 的核心用法。
总结要点:
- Stack 容器是实现层叠布局的核心工具,子组件按书写顺序自下而上堆叠。
- LoadingProgress 提供系统级的加载动画效果,零依赖、高性能。
- @State 状态管理驱动 UI 条件渲染,实现加载层的精准显隐。
- 生命周期管理确保定时器等资源的正确释放,防止内存泄漏。
- 全屏覆盖需要子组件设置 100% 宽高并调用
expandSafeArea。
这套布局方案代码量少、可读性强、性能优异,是鸿蒙原生开发中实现加载等待效果的推荐方案。无论是简单的数据加载,还是复杂的多阶段初始化流程,都可以在此基础上灵活扩展。
本文配套示例代码已托管在项目中,欢迎运行体验。如有疑问或建议,欢迎留言讨论。
参考资料
更多推荐



所有评论(0)