鸿蒙新特性:LoadingProgress 加载状态与四态管理模式
引言
在移动应用开发中,界面状态管理是最基础也最容易忽视的问题之一。一个网络请求可能产生四种结果:正在加载中、加载成功返回数据、加载成功但数据为空、加载失败。如果每种状态都缺乏妥善的 UI 呈现,用户就会面对一片空白、永久转圈、或者莫名其妙的死屏。
HarmonyOS NEXT 提供了 LoadingProgress 组件作为加载状态的视觉指示器,但要构建一个完整的用户体验,仅仅有一个旋转动画远远不够。本文将通过一个"消息中心"实战 Demo,深入讲解四种加载状态(Loading / Success / Empty / Error)的 UI 设计模式和状态切换管理,帮助你构建让用户安心、清晰、可控的数据加载体验。
读完本文你将能够:
- 使用
LoadingProgress组件构建专业的加载动画 - 掌握骨架屏(Skeleton Screen)占位技术及其实现
- 理解空状态和错误状态的设计原则
- 学会用状态机管理页面状态切换
- 获得一套可复用的四态加载管理模式
LoadingProgress:ArkUI 的加载指示器
基本用法
LoadingProgress 是 ArkUI 提供的旋转式加载指示器组件,用于告知用户当前正在进行后台操作:
LoadingProgress()
.width(40)
.height(40)
.color('#1677FF')
LoadingProgress 的核心属性:
| 属性 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| width | Length | 组件宽度 | 系统默认 |
| height | Length | 组件高度 | 系统默认 |
| color | ResourceColor | 旋转线条颜色 | 系统主题色 |
| enableLoading | boolean | 是否启用加载动画 | true |
它是 ArkUI 中最"安静"的组件之一——不需要复杂配置,不需要状态管理,放上去就开始旋转,拿下来就停止。这种朴素的 API 设计让开发者可以将精力集中在"什么时候显示它"和"什么时候替换它"这两个核心问题上。
LoadingProgress vs Progress
很多开发者容易混淆 LoadingProgress 和 Progress:
| 特性 | LoadingProgress | Progress |
|---|---|---|
| 用途 | 表示"正在进行中"的等待 | 表示"已完成比例"的进度 |
| 形态 | 不停旋转的圆环 | 可填充的条形/环形 |
| 是否确定性 | 不确定(indeterminate) | 确定(determinate,有 value) |
| 典型场景 | 网络请求中、数据加载中 | 文件下载、视频缓冲、任务完成度 |
简单说:如果你不知道还要多久,用 LoadingProgress;如果你知道完成了多少,用 Progress。
四态加载管理模式
一个完整的数据加载 UI 需要处理四种状态:
状态一:Loading(加载中)
这是用户发起请求后看到的第一个状态。UI 需要传达两个信息:“系统正在工作” 和 “请稍等”。
LoadingProgress + 骨架屏是最佳实践:
// Loading 态的设计模式
Column() {
LoadingProgress()
.width(40).height(40)
.color('#1677FF')
.margin({ bottom: 14 })
Text('正在加载消息...')
.fontSize(13).fontColor('#BBBBCC')
.margin({ bottom: 24 })
// 骨架占位卡片:让用户预览内容布局
ForEach([0, 1, 2], (item: number) => {
Row() {
Row() // 圆形头像占位
.width(44).height(44)
.borderRadius(22)
.backgroundColor('#E8E8EE')
Column() {
Row() // 标题行占位
.width('60%').height(13)
.borderRadius(4)
.backgroundColor('#E8E8EE')
Row() // 摘要行占位
.width('85%').height(11)
.borderRadius(4)
.backgroundColor('#F0F0F5')
}
.layoutWeight(1).margin({ left: 12 })
}
.padding(14).borderRadius(10)
.backgroundColor('#FFFFFF')
})
}
骨架屏的核心价值在于:用户在看到 LoadingProgress 的同时,也能预览内容的大致结构。这降低了"等待的不确定性",让用户知道页面即将出现的内容是什么样的——三个圆角头像卡片、每张卡片有标题行和摘要行。相比于一片空白加一个转圈,骨架屏让等待体验从"被动忍受"升级为"主动期待"。
状态二:Success(加载成功)
这是最常规的状态——数据已返回,渲染完整的消息列表:
ForEach(this.messages, (msg: MessageItem, idx: number) => {
Row() {
// 头像
Row() {
Text(msg.avatar)
.fontSize(18).fontColor('#FFFFFF')
}
.width(44).height(44).borderRadius(22)
.backgroundColor(msg.unread ? '#1677FF' : '#CCCCDD')
.justifyContent(FlexAlign.Center)
// 标题 + 摘要
Column() {
Row() {
Text(msg.title)
.fontSize(15)
.fontWeight(msg.unread ? FontWeight.Bold : FontWeight.Normal)
.layoutWeight(1)
Text(msg.time).fontSize(11).fontColor('#CCCCDD')
}
Text(msg.subtitle)
.fontSize(12).fontColor('#999999')
.maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
}
.layoutWeight(1).margin({ left: 12 })
// 未读标记
if (msg.unread) {
Row().width(8).height(8)
.borderRadius(4)
.backgroundColor('#FF4D4F')
}
}
.padding(14).borderRadius(10).backgroundColor('#FFFFFF')
}, (msg: MessageItem) => msg.id.toString())
Success 态的设计要点:
- 信息层次清晰:标题(bold)+ 时间 + 摘要,从上到下优先级递减
- 未读状态可区分:蓝色头像 vs 灰色头像、粗体 vs 常规字重、红点标记
- 与骨架屏布局一致:Success 态的卡片结构与 Loading 态的骨架占位一一对应,视觉上的平滑过渡避免了"跳变感"
状态三:Empty(无数据)
空状态是最容易被忽视的状态。很多应用的"暂无数据"只是一行灰色小字,用户看了会怀疑是 Bug 还是真的没数据。
良好设计的空状态包含三个要素:
- 视觉插图:一个醒目的大图标或插画(本文使用 emoji 作为示意)
- 明确说明:用一句话告诉用户当前的状态
- 行动引导:提供一个按钮引导用户采取下一步行动
Column() {
Text('📋').fontSize(64) // 视觉焦点
Text('暂无消息') // 状态说明
.fontSize(17).fontWeight(FontWeight.Bold)
Text('还没有收到任何消息,点击下方按钮加载示例数据') // 引导文案
.fontSize(12).fontColor('#BBBBCC')
Row() {
Text('加载示例数据') // 行动按钮
.fontColor('#FFFFFF')
.padding({ top: 10, bottom: 10, left: 28, right: 28 })
.borderRadius(20).backgroundColor('#1677FF')
.onClick(() => { this.loadSampleData(); })
}
}
.justifyContent(FlexAlign.Center)
关键原则:不把用户困在空状态。每个空状态页面都应该有一个"出口"——一个引导用户离开空状态的按钮或链接。
状态四:Error(加载失败)
错误状态需要传达三层信息:“出了什么问题”、“用户该怎么做”、“可以重试”:
Column() {
Text('⚠️').fontSize(64) // 错误图标
Text('加载失败') // 状态说明
.fontSize(17).fontWeight(FontWeight.Bold)
Text('网络异常,请检查网络连接后重试') // 原因 + 建议
.fontSize(12).fontColor('#BBBBCC')
Row() {
Text('重新加载') // 重试按钮
.fontColor('#FFFFFF')
.padding({ top: 10, bottom: 10, left: 28, right: 28 })
.borderRadius(20).backgroundColor('#1677FF')
.onClick(() => { this.loadSampleData(); })
}
}
Error 态设计要点:
- 不要用技术术语:用户不关心"HTTP 500"或"JSON 解析失败",只需要知道"加载失败"和"可以重试"
- 重试按钮要突出:这是 Error 态最重要的交互元素,用品牌色/主色调强调
- 可以提供降级方案:对于关键功能,除了重试还可以提供"查看缓存数据"等备选
空状态 vs 错误状态
很多开发者将空状态和错误状态混为一谈,这对用户是不友好的。两者的本质区别:
| 维度 | Empty | Error |
|---|---|---|
| 原因 | 确实没有数据 | 无法获取数据 |
| 用户预期 | “哦,还没有消息” | “嗯?出问题了” |
| 解决方案 | 引导创建/添加 | 重试获取 |
| 按钮文案 | “加载示例/创建” | “重试/重新加载” |
| 视觉色调 | 温和(中性色) | 警示(暖色) |
状态机设计
四态之间的转换不是随意的,它们构成一个有限状态机:
aboutToAppear
↓
LOADING ──模拟网络延迟──→ SUCCESS
│ │
│ "清空消息"
│ ↓
│ EMPTY
│ │
│ "加载示例数据"
"模拟错误" │
│ │
↓ ↓
LOADING ←──────────────────┘
│
模拟延迟
↓
ERROR ──"重试"──→ LOADING → SUCCESS
在代码中,这个状态机通过 loadState 这个单一的 @State 变量来驱动:
@State loadState: number = 0; // 0=Loading, 1=Success, 2=Empty, 3=Error
每次状态切换,只需修改这一个值,UI 就会自动切换到对应的渲染分支。
状态转换的核心方法:
// 加载数据:任何状态 → Loading → Success
loadSampleData(): void {
this.loadState = 0; // 先进入 Loading
this.messages = []; // 清空旧数据
setTimeout(() => {
this.messages = this.getSampleMessages(); // 模拟数据
this.loadState = 1; // 切换到 Success
}, 1500);
}
// 清空数据:Success → Empty(无需 Loading 过渡)
clearMessages(): void {
this.messages = [];
this.loadState = 2; // 直接进入 Empty
}
// 模拟错误:任何状态 → Loading → Error
simulateError(): void {
this.loadState = 0; // 先进入 Loading
this.messages = [];
setTimeout(() => {
this.messages = [];
this.loadState = 3; // 切换到 Error
}, 1000);
}
关键设计原则:
- 网络操作前必须先进入 Loading:让用户知道系统在做什么
- 纯本地操作无需 Loading:如"清空消息"直接到 Empty
- 状态数组和数据数组同步管理:切到 Loading/Empty/Error 时清空 messages,切到 Success 时填充


状态徽章:让当前状态对外可见
在 Demo 的标题栏中,我们放置了一个状态徽章,实时显示当前处于哪种状态:
Row() {
Circle({ width: 7, height: 7 })
.fill(this.getStateColor()) // 状态对应颜色
.margin({ right: 5 })
Text(this.getStateLabel()) // "加载中"/"已加载"/"无数据"/"加载失败"
.fontSize(11)
.fontColor('#FFFFFFCC')
}
.padding({ top: 5, bottom: 5, left: 10, right: 10 })
.borderRadius(10)
.backgroundColor('#FFFFFF20')
颜色映射表:
getStateColor(): string {
if (this.loadState === 0) return '#1677FF'; // 蓝色 - 加载中
if (this.loadState === 1) return '#52C41A'; // 绿色 - 成功
if (this.loadState === 2) return '#FAAD14'; // 橙色 - 空
return '#FF4D4F'; // 红色 - 错误
}
状态徽章看似是一个小细节,但它有两个重要作用:
- 让 Demo 意图清晰:在演示四态切换时,开发者和用户都能明确看到当前状态
- 为生产应用提供设计参考:在产品中加入状态指示器(如"网络请求中"小圆点),有助于用户理解应用的当前行为
底部控制栏:状态切换的操作面板
Demo 的底部控制栏提供了两个操作按钮,用于触发状态切换:
主操作按钮:根据当前状态动态切换文案和功能:
getLoadButtonLabel(): string {
if (this.loadState === 1) return '清空消息'; // Success → Empty
if (this.loadState === 3) return '重试'; // Error → Loading → Success
return '加载示例数据'; // Empty → Loading → Success
}
模拟错误按钮:仅在 Success 状态下显示:
if (this.loadState === 1) {
Row() {
Text('模拟错误')
.fontColor('#FF4D4F')
.border({ width: 1, color: '#FF4D4F33' })
.onClick(() => { this.simulateError(); })
}
}
这种按钮的动态显隐和文案切换,让控制栏始终保持简洁——用户只看到当前状态下有意义的操作。
骨架屏实现技术
骨架屏(Skeleton Screen)是一种在加载过程中展示内容占位的技术。它用灰色块模拟即将出现的内容布局,让页面在加载时看起来"已经有内容了"。
实现骨架屏不需要特殊组件,使用普通的布局组件 + 背景色即可:
// 头像骨架:圆角矩形模拟头像
Row()
.width(44).height(44)
.borderRadius(22)
.backgroundColor('#E8E8EE')
// 标题骨架:短横条模拟标题文字
Row()
.width('60%').height(13)
.borderRadius(4)
.backgroundColor('#E8E8EE')
// 摘要骨架:长横条模拟摘要文字
Row()
.width('85%').height(11)
.borderRadius(4)
.backgroundColor('#F0F0F5')
三点技巧:
- 宽度用百分比:模拟标题用 60% 宽度(标题通常不是满宽的),模拟摘要用 85%(更长的文本行),更接近真实内容
- 颜色分层:标题骨架稍深(
#E8E8EE),摘要骨架稍浅(#F0F0F5),模仿真实文本的视觉权重差异 - 与 Success 态布局完全对齐:骨架中头像 44×44、内边距 14、圆角 10,与 Success 态的消息卡片完全一致
完整代码中的关键技巧
1. 使用 setTimeout 模拟异步操作
在真实应用中,Loading 状态是被网络请求驱动的。Demo 中使用 setTimeout 模拟:
setTimeout(() => {
this.messages = this.getSampleMessages();
this.loadState = 1;
}, 1500);
1500ms 的延迟量足够让用户感知到 Loading 状态,但又不至于等待太久。在生产代码中,这个位置应替换为 http.createHttp().request() 等实际网络请求。
2. 状态与数据的一致性
Loading / Empty / Error 状态下,messages 数组应为空;Success 状态下,messages 包含实际数据。每次状态切换都要同步更新两者:
// 永远以"先设状态,再等结果"的模式操作
this.loadState = 0; // 1. 先显示 Loading
this.messages = []; // 2. 清空旧数据
// ... 网络请求 ...
this.messages = result; // 3. 填充新数据
this.loadState = 1; // 4. 显示 Success
3. 按钮状态驱动
底部控制栏的主按钮根据当前状态改变行为和样式:
handleMainAction(): void {
if (this.loadState === 1) {
this.clearMessages(); // Success → Empty
} else {
this.loadSampleData(); // Empty/Error → Loading → Success
}
}
这是一种"语境响应式"的交互模式——同一个按钮,不同状态下执行不同操作。它减少了按钮数量,保持了界面简洁。
总结
本文通过一个"消息中心"Demo,系统讲解了四种加载状态的 UI 设计和管理方法。我们从 LoadingProgress 组件的基本用法出发,逐步构建了骨架屏占位、空状态引导、错误状态重试等完整的用户体验闭环。
四态管理模式的核心可概括为以下几点:
- Loading 不等于只放一个转圈:加上骨架屏,让用户预览内容结构
- Empty 要有出口:每个空状态页面都要有引导用户"离开空状态"的按钮
- Error 要讲人话:用用户能理解的语言描述问题和解决方案,不要暴露技术细节
- 状态切换要走状态机:网络操作必须有 Loading 过渡;本地操作可以直达目标状态
- 数据与状态同生命周期:切出 Success 态时清空数据,切回 Success 时重新填充
在真实的商业应用中,你还可以在此基础上扩展到 5 态(增加"网络离线"状态)或 6 态(增加"需要登录/授权"状态)。核心的设计哲学是不变的:让用户在任何状态下都感到安心和有控制感。
更多推荐



所有评论(0)