
引言
空态页面是应用中不可或缺的一部分,当用户没有数据时,空态页面可以引导用户进行下一步操作。本文将介绍如何设计和实现各种空态页面,包括:
- 列表空态
- 搜索空态
- 收藏空态
- 网络错误空态
- 个性化空态
通过本文,你将掌握如何设计优雅的空态页面。
学习目标
完成本文后,你将能够:
- ✅ 设计列表空态页面
- ✅ 实现搜索空态
- ✅ 创建收藏空态
- ✅ 处理网络错误空态
- ✅ 添加个性化空态
需求分析
功能模块设计
| 模块 | 功能描述 | 技术要点 |
|---|---|---|
| 列表空态 | 列表无数据时的展示 | 图片+文字+按钮 |
| 搜索空态 | 搜索无结果时的展示 | 搜索图标+提示文字 |
| 收藏空态 | 收藏为空时的展示 | 收藏图标+引导文字 |
| 网络错误 | 网络异常时的展示 | 刷新按钮+错误提示 |
| 个性化空态 | 根据场景定制空态 | 定制图标和文案 |
核心实现
步骤1: 空态组件封装
完整代码
typescript
// components/EmptyState.ets
@Component
export struct EmptyState {
// 空态类型
@Prop type: EmptyStateType = 'default';
// 自定义标题
@Prop title?: string;
// 自定义描述
@Prop description?: string;
// 自定义按钮文字
@Prop buttonText?: string;
// 自定义按钮点击事件
@Prop onButtonClick?: () => void;
/**
* 获取空态配置
*/
private getConfig(): EmptyStateConfig {
const configs: Record<EmptyStateType, EmptyStateConfig> = {
default: {
icon: $r('app.media.ic_empty_default'),
title: '暂无数据',
description: '暂无相关内容',
buttonText: '去看看',
backgroundColor: '#F8F7F2'
},
list: {
icon: $r('app.media.ic_empty_list'),
title: '暂无内容',
description: '还没有相关内容,快来添加吧',
buttonText: '添加内容',
backgroundColor: '#FFFFFF'
},
search: {
icon: $r('app.media.ic_empty_search'),
title: '未找到结果',
description: '换个关键词试试吧',
buttonText: '重新搜索',
backgroundColor: '#FFFFFF'
},
collect: {
icon: $r('app.media.ic_empty_collect'),
title: '暂无收藏',
description: '快去收藏喜欢的内容吧',
buttonText: '去探索',
backgroundColor: '#FFFFFF'
},
network: {
icon: $r('app.media.ic_empty_network'),
title: '网络异常',
description: '请检查网络连接后重试',
buttonText: '刷新',
backgroundColor: '#F8F7F2'
},
quiz: {
icon: $r('app.media.ic_empty_quiz'),
title: '暂无测验记录',
description: '快去参加测验吧',
buttonText: '去测验',
backgroundColor: '#FFFFFF'
},
article: {
icon: $r('app.media.ic_empty_article'),
title: '暂无文章',
description: '暂无相关文章',
buttonText: '去浏览',
backgroundColor: '#FFFFFF'
}
};
return configs[this.type] || configs.default;
}
/**
* 构建UI
*/
build() {
const config = this.getConfig();
Column({ space: 16 }) {
// 图标
Image(this.title ? $r('app.media.ic_empty_default') : config.icon)
.width(120)
.height(120)
.opacity(0.5)
// 标题
Text(this.title || config.title)
.fontSize(16)
.fontColor('#999999')
// 描述
Text(this.description || config.description)
.fontSize(14)
.fontColor('#BBBBBB')
// 按钮
if (this.buttonText || config.buttonText) {
Button(this.buttonText || config.buttonText)
.width(120)
.height(40)
.backgroundColor('#4A9B6D')
.fontColor('#FFFFFF')
.borderRadius(20)
.margin({ top: 16 })
.onClick(() => {
if (this.onButtonClick) {
this.onButtonClick();
}
})
}
}
.width('100%')
.height('100%')
.backgroundColor(config.backgroundColor)
.justifyContent(FlexAlign.Center)
}
}
type EmptyStateType = 'default' | 'list' | 'search' | 'collect' | 'network' | 'quiz' | 'article';
interface EmptyStateConfig {
icon: Resource;
title: string;
description: string;
buttonText: string;
backgroundColor: string;
}
代码解析
1. 空态类型
- default: 默认空态
- list: 列表空态
- search: 搜索空态
- collect: 收藏空态
- network: 网络错误空态
- quiz: 测验空态
- article: 文章空态
2. 配置对象
- icon: 图标资源
- title: 标题文字
- description: 描述文字
- buttonText: 按钮文字
- backgroundColor: 背景颜色
步骤2: 使用空态组件
typescript
// 在页面中使用空态组件
@Entry
@Component
struct MyPage {
@State data: any[] = [];
@State isLoading: boolean = false;
@State hasError: boolean = false;
build() {
Column() {
if (this.isLoading) {
// 加载中状态
Column() {
LoadingProgress().width(40).height(40)
Text('加载中...').fontSize(14).fontColor('#999').margin({ top: 16 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
} else if (this.hasError) {
// 网络错误空态
EmptyState({
type: 'network',
onButtonClick: () => {
this.loadData();
}
})
} else if (this.data.length === 0) {
// 数据为空空态
EmptyState({
type: 'list',
onButtonClick: () => {
// 跳转到添加页面
}
})
} else {
// 正常数据展示
List() {
ForEach(this.data, (item) => {
ListItem() {
// 列表项
}
})
}
}
}
.width('100%')
.height('100%')
}
loadData() {
// 加载数据逻辑
}
}
设计要点:
- 根据状态展示不同的空态
- 加载中状态
- 网络错误状态
- 数据为空状态
步骤3: 搜索空态
typescript
/**
* 搜索空态组件
*/
@Component
struct SearchEmptyState {
@Prop keyword: string = '';
build() {
Column({ space: 16 }) {
// 搜索图标
Stack({ alignContent: Alignment.Center }) {
Circle()
.width(100)
.height(100)
.fillColor('#E8F5E9')
Image($r('app.media.ic_search'))
.width(40)
.height(40)
.fillColor('#4A9B6D')
}
// 标题
Text(`未找到 "${this.keyword}" 相关结果`)
.fontSize(16)
.fontColor('#999999')
// 描述
Text('换个关键词试试吧')
.fontSize(14)
.fontColor('#BBBBBB')
// 热门搜索标签
Column({ space: 12 }) {
Text('热门搜索')
.fontSize(14)
.fontColor('#666666')
Wrap({ spacing: 8 }) {
['立春', '清明', '夏至', '冬至', '节气养生', '传统习俗'].forEach((tag) => {
Text(tag)
.fontSize(13)
.fontColor('#4A9B6D')
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor('#E8F5E9')
.borderRadius(20)
})
}
}
.margin({ top: 16 })
}
.width('100%')
.padding(24)
}
}
设计要点:
- 显示搜索关键词
- 热门搜索标签
- 引导用户尝试其他关键词
步骤4: 网络错误空态
typescript
/**
* 网络错误空态组件
*/
@Component
struct NetworkErrorState {
@Prop onRetry: () => void = () => {};
build() {
Column({ space: 16 }) {
// 网络图标
Stack({ alignContent: Alignment.Center }) {
Circle()
.width(100)
.height(100)
.fillColor('#FFF5F5')
Image($r('app.media.ic_wifi_off'))
.width(40)
.height(40)
.fillColor('#FF5252')
}
// 标题
Text('网络连接异常')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
// 描述
Text('请检查网络连接后重试')
.fontSize(14)
.fontColor('#999999')
// 刷新按钮
Button('刷新')
.width(120)
.height(44)
.backgroundColor('#4A9B6D')
.fontColor('#FFFFFF')
.fontSize(16)
.borderRadius(22)
.onClick(() => {
this.onRetry();
})
// 提示信息
Text('也可以尝试离线模式')
.fontSize(12)
.fontColor('#BBBBBB')
.margin({ top: 8 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.backgroundColor('#F8F7F2')
}
}
设计要点:
- 红色错误主题
- 刷新按钮
- 离线模式提示
步骤5: 收藏空态
typescript
/**
* 收藏空态组件
*/
@Component
struct CollectEmptyState {
@Prop onExplore: () => void = () => {};
build() {
Column({ space: 16 }) {
// 收藏图标
Stack({ alignContent: Alignment.Center }) {
Circle()
.width(100)
.height(100)
.fillColor('#FFF8E1')
Image($r('app.media.ic_collect'))
.width(40)
.height(40)
.fillColor('#FFB300')
}
// 标题
Text('暂无收藏')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
// 描述
Text('快去收藏喜欢的节气和文章吧')
.fontSize(14)
.fontColor('#999999')
// 去探索按钮
Button('去探索')
.width(120)
.height(44)
.backgroundColor('#4A9B6D')
.fontColor('#FFFFFF')
.fontSize(16)
.borderRadius(22)
.onClick(() => {
this.onExplore();
})
// 推荐内容
Column({ space: 12 }) {
Text('为你推荐')
.fontSize(14)
.fontColor('#666666')
.alignSelf(ItemAlign.Center)
Column({ space: 8 }) {
['立春 - 春天的开始', '清明 - 缅怀先人', '夏至 - 最长的白天'].forEach((item) => {
Row({ space: 8 }) {
Image($r('app.media.ic_star'))
.width(16)
.height(16)
.fillColor('#FFB300')
Text(item)
.fontSize(14)
.fontColor('#666666')
}
})
}
}
.margin({ top: 16 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.backgroundColor('#FFFFFF')
}
}
设计要点:
- 黄色主题(收藏相关)
- 推荐内容列表
- 引导用户去探索
本章小结
核心知识点
本文完成了空态页面的设计:
1. 空态组件封装
- 统一的空态组件
- 支持多种类型
- 可自定义配置
2. 搜索空态
- 显示搜索关键词
- 热门搜索标签
3. 网络错误空态
- 错误提示
- 刷新按钮
- 离线模式提示
4. 收藏空态
- 推荐内容
- 引导用户操作
系列总结
🎉 "节气通"应用开发系列文章第二篇圆满完成!
通过第二篇文章,你已经学会了:
已完成的页面 :
-
个人中心页开发 - 用户信息、学习统计、功能入口
-
设置页开发 - 基础设置、数据管理、账号安全
-
隐私设置与服务模式 - 权限管理、数据授权、隐私信息
-
收藏功能实现 - 收藏列表、添加/删除收藏
-
学习记录页面 - 学习统计、学习日历、成就系统
-
知识问答页面 - 问答列表、提问、回答功能
-
意见反馈页面 - 反馈表单、问题分类、图片上传
-
关于页面与隐私政策 - 应用介绍、版本信息、法律信息
-
空态页面设计 - 各种空态场景的设计
下一步预告
第二篇已经完成!在下一篇中,我们将学习:
- 组件封装与复用
- ArticleCard组件封装
- HolidayCard组件封装
- CategoryGrid组件封装
- Timeline组件封装
节气通应用已发布上线,可在应用市场下载体验
相关链接
- 项目源码 : Atomgit仓库