当前位置: 首页 > news >正文

20 EmptyState 空状态设计:插画、动画与交互反馈

20 EmptyState 空状态设计:插画、动画与交互反馈

前言

图:20 EmptyState 空状态设计:插画、动画与交互反馈 运行效果截图(HarmonyOS NEXT)

在 App 开发中,空状态(Empty State)是用户首次使用或数据尚未生成时的关键体验节点。一个优秀的空状态页不仅要告诉用户"这里还没有内容",更要引导用户采取行动来填充内容。

本文以"鹿鹿·笔迹心理分析"项目中的 [EmptyState.ets](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/common/components/EmptyState.ets) 组件为例,全面解析鸿蒙空状态组件的设计思路——从插画布局到入场动画,从呼吸脉冲到定时器管理。

鸿蒙官方·aboutToAppear 生命周期:developer.huawei.com
项目源码仓库:harmony-app GitHub

图:EmptyState 三要素——插画区(入场动画)+ 文字区 + CTA 按钮(呼吸脉冲),aboutToAppear 触发两段动画

EmptyState 组件

插画区 Stack

文字区

CTA 按钮

纸张 Column -3°旋转

波浪线三层透明度

羽毛笔绝对定位

title @Prop

subtitle @Prop

ButtonType.Capsule

呼吸脉冲 setInterval

onCtaClick 回调

生命周期

aboutToAppear 触发入场动画

aboutToDisappear 清除定时器

一、空状态组件的设计原则

1.1 三个核心要素

一个完整的空状态应包含以下三个要素:

要素作用EmptyState 实现
插画/图标视觉提示,唤起情感共鸣手绘风纸笔插画(Stack 模拟)
提示文字说明当前状态title+subtitle双行文字
行动号召(CTA)引导用户操作胶囊按钮 + 呼吸脉冲动画
@Componentexportstruct EmptyState{// 三要素@Proptitle:string='还没有分析记录,快来试试吧'// 提示文字@Propsubtitle:string='上传一段手写笔记 · 让 AI 读懂你'@PropctaText:string='拍下第一笔'// CTA 按钮文字onCtaClick?:()=>void// CTA 回调}

1.2 UI 2.0 中的空状态规范

鸿蒙设计规范推荐的空状态原则:

  1. 友善而非冷漠:使用插画而非纯文字
  2. 引导而非指责:“还没有内容” > “内容为空”
  3. 单一 CTA:只提供一个主要操作入口
  4. 动态而非静止:加入细微动画提升浏览体验

提示:空状态中的插画应保持品牌一致性——色彩、风格、语气都需要与 App 整体调性统一。

二、EmptyState 的 UI 结构

2.1 整体布局

[EmptyState.ets](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/common/components/EmptyState.ets) 采用 Column 从上到下依次排列插画区、文字区、按钮区:

build(){Column(){// ========== 插画区(手绘风纸笔) ==========Stack(){// 双层斜放纸张Column().width(140).height(110).backgroundColor(AppColors.PAPER).borderRadius(AppRadius.S).border({width:1,color:AppColors.LINE}).rotate({angle:-3})// 轻微旋转,模拟纸张随意摆放.shadow({radius:12,color:'rgba(61,53,46,.08)',offsetY:4})// 三道笔迹波浪线Column(){Text('~~~~').fontSize(20).fontColor(AppColors.PRIMARY).opacity(0.85)Text('~~~').fontSize(18).fontColor(AppColors.WARM).opacity(0.7).margin({top:8})Text('~~').fontSize(16).fontColor(AppColors.QUIET).opacity(0.55).margin({top:8})}.width(140).height(110).justifyContent(FlexAlign.Center).rotate({angle:2})// 右上角羽毛笔装饰Text('✒').fontSize(32).fontColor(AppColors.PRIMARY_DEEP).position({x:110,y:-10})}.width(180).height(140).opacity(this.illustrationOpacity)// 入场动画控制.translate({y:this.illustrationOffset})// ========== 文字区 ==========Text(this.title).fontSize(AppFonts.SUBTITLE).fontColor(AppColors.TEXT).margin({top:AppSpacing.XL}).letterSpacing(1)Text(this.subtitle).fontSize(AppFonts.CAPTION).fontColor(AppColors.TEXT_2).margin({top:AppSpacing.XS}).letterSpacing(1)// ========== CTA 按钮 ==========Button(this.ctaText).type(ButtonType.Capsule).backgroundColor(AppColors.PRIMARY).fontColor(Color.White).fontSize(AppFonts.BODY).padding({left:28,right:28,top:12,bottom:12}).margin({top:AppSpacing.XL}).scale({x:this.pulseScale,y:this.pulseScale})// 呼吸缩放.animation({duration:800,curve:Curve.EaseOut}).onClick(()=>this.onCtaClick?.())}.width('100%').height('100%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).backgroundColor(AppColors.BG)}

2.2 手绘风插画的设计细节

插画通过 Stack 层叠 + 基础组件模拟手绘纸张效果:

// 纸张底部 — 双层斜放(-3° 旋转)Column().width(140).height(110).backgroundColor(AppColors.PAPER)// 米黄纸色.borderRadius(AppRadius.S)// 4vp 小圆角.border({width:1,color:AppColors.LINE})// 淡棕边框.rotate({angle:-3})// -3° 旋转(模拟随意摆放)// 笔迹波浪 — 三层递减透明度Text('~~~~').opacity(0.85)// 第一行:85%Text('~~~').opacity(0.7)// 第二行:70%Text('~~').opacity(0.55)// 第三行:55%

设计细节

  1. rotate({ angle: -3 })— 纸张轻微旋转,打破"太整齐"的机械感
  2. 三道波浪线长度递减(4/3/2 个波浪符),透明度递减
  3. 羽毛笔position({ x: 110, y: -10 })超出 Stack 边界,增加趣味性

三、入场动画实现

3.1 aboutToAppear 生命周期

空状态的入场动画在aboutToAppear中触发——这是组件创建后、首次渲染前调用的生命周期方法:

// 入场动画状态@StateillustrationOpacity:number=0// 插画透明度(从 0 到 1)@StateillustrationOffset:number=20// 插画 Y 偏移(从 20 到 0)aboutToAppear():void{// 插画淡入 + 上移animateTo({duration:600,curve:Curve.FastOutSlowIn},()=>{this.illustrationOpacity=1// 透明度 0→1this.illustrationOffset=0// 偏移 20→0})// 启动呼吸脉冲定时器this.pulseTimer=setInterval(()=>{animateTo({duration:800,curve:Curve.EaseOut},()=>{this.pulseScale=this.pulseScale>1.05?1:1.06})},1600)}

动画效果

  1. 插画从下方 20vp 处淡入上移(600ms)
  2. CTA 按钮开始循环呼吸缩放(幅度 1.0↔1.06,周期 1600ms)
  3. 文字区无动画,自然渲染

提示:入场动画的 duration(600ms)略短于呼吸脉冲的周期(1600ms),确保入场完成后才开始呼吸,避免动画重叠。

四、呼吸脉冲动画

4.1 setInterval + animateTo 循环

脉冲动画使用setInterval定时器 +animateTo显式动画的递归模式:

privatepulseTimer:number=-1@StatepulseScale:number=1aboutToAppear():void{this.pulseTimer=setInterval(()=>{animateTo({duration:800,curve:Curve.EaseOut},()=>{this.pulseScale=this.pulseScale>1.05?1:1.06})},1600)// 每 1.6 秒触发一次脉冲}

时序分析

时间点事件pulseScale按钮视觉
T=0ms组件创建1.0原始大小
T=1600ms第一次脉冲1.0 → 1.06(800ms)缓慢放大
T=2400ms第一次复位1.06 → 1.0(800ms)缓慢恢复
T=3200ms第二次脉冲1.0 → 1.06(800ms)再放大
持续循环持续呼吸

4.2 定时器清理

组件的生命结束时必须清理定时器,防止内存泄漏:

aboutToDisappear():void{// 清除脉冲定时器if(this.pulseTimer!==-1){clearInterval(this.pulseTimer)this.pulseTimer=-1}}

关键实践:任何使用setInterval/setTimeout的组件,都必须在aboutToDisappear中清理定时器。否则组件虽然销毁,定时器仍在运行,导致内存泄漏和潜在的逻辑错误。

五、CTA 按钮交互

5.1 回调模式

CTA 按钮不内嵌具体业务逻辑,而是通过回调暴露给父组件:

// 子组件:EmptyStateButton(this.ctaText).onClick(()=>this.onCtaClick?.())// 可选链调用// 父组件:HomePageEmptyState({ctaText:'写下第一笔',onCtaClick:()=>{this.getUIContext().getRouter().pushUrl({url:'pages/WritePage'})}})

5.2 按钮样式参数

参数设计考量
typeButtonType.Capsule胶囊形最友善、最现代
backgroundColorAppColors.PRIMARY品牌主色(暖棕),传递信任感
fontColorColor.White最大对比度,确保可读性
fontSizeAppFonts.BODY(14)正文字号,不喧宾夺主
padding左右 28 / 上下 12充足留白,触控友好
scale + animation呼吸脉冲吸引力但不打扰

六、EmptyState 在不同页面的应用

6.1 首页空状态

// HomePage.ets — 用户首次进入时展示if(this.recentItems.length===0){EmptyState({title:'还没有分析记录,快来试试吧',ctaText:'拍下第一笔',onCtaClick:()=>{this.getUIContext().getRouter().pushUrl({url:'pages/CapturePage'})}})}

6.2 档案页空状态

// ArchivePage.ets — 无档案时if(archives.length===0){EmptyState({title:'还没有创建任何档案',subtitle:'分析完成后自动生成档案',ctaText:'去分析',onCtaClick:()=>{/* 导航到分析页 */}})}

提示:同一个 EmptyState 组件通过不同的title/subtitle/ctaText组合,可以覆盖"无记录"、“无档案”、"无结果"等多个空状态场景。

七、空状态组件的可配置性

7.1 可配置参数一览

参数类型默认值场景覆盖
titlestring“还没有分析记录”首页无数据
subtitlestring“上传一段手写笔记”无资源
ctaTextstring“拍下第一笔”操作引导
onCtaClickcallbackundefined操作回调

7.2 扩展建议

空状态组件可以进一步扩展:

// 扩展版本(未来可考虑)@Componentexportstruct EmptyState{@Proptitle:string=''@Propsubtitle:string=''@PropctaText:string=''@PropillustrationType:IllustrationType=IllustrationType.PAPER// 插画类型@PropshowAnimation:boolean=true// 是否播放动画onCtaClick?:()=>void}

八、EmptyState 的设计启示

8.1 空状态的层次感

一个好的空状态在视觉上应有明确的层次感:

  1. 视觉焦点:插画区(180×140vp,占据中心位置)
  2. 信息传达:文字区(title + subtitle,居中排列)
  3. 行动引导:CTA 按钮(Capsule 胶囊,带呼吸动画)

8.2 动画的克制

在空状态中使用动画需要克制:

  • 插画入场动画只播放一次
  • 按钮呼吸脉冲幅度控制在1.0↔1.06(不可过大)
  • 脉冲周期1600ms(不急促,不拖延)

动画是为体验服务的——过度的动画会变成骚扰,让用户想快速离开页面。

8.3 空状态的性能考量

优化点说明影响
setInterval必须清理防止内存泄漏和定时器堆积直接影响应用稳定性
插画用基础组件模拟无需加载图片资源,渲染更快减少首次渲染耗时
动画使用animateTo框架统一调度,帧率更稳定动画流畅度
@State最小化只声明 3 个动画变量减少不必要的重渲染

总结

本文从 [EmptyState.ets](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/common/components/EmptyState.ets) 的完整代码出发,深入解析了鸿蒙空状态组件的设计方法:

  1. 三要素结构:插画 + 文字 + CTA 按钮,覆盖空状态的核心需求
  2. 入场动画aboutToAppear中触发淡入上移动画,600ms 自然过渡
  3. 呼吸脉冲setInterval+animateTo实现循环缩放,1700ms 周期
  4. 定时器管理aboutToDisappear中清理定时器,防止内存泄漏
  5. 回调模式:CTA 操作通过onCtaClick回调交由父组件处理
  6. 可配置性:通过多个 @Prop 参数适配不同场景的空状态

下一篇文章将介绍PosterTemplate 分享海报模板,解析主题系统、排版布局和二维码占位设计。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


参考资源:

  • aboutToAppear 生命周期
  • setInterval 定时器管理
  • animateTo 显式动画
  • Button 组件参考
  • [EmptyState 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/common/components/EmptyState.ets)
  • ArkUI 组件生命周期
  • 鸿蒙设计规范:空状态
  • HarmonyOS 官方示例
  • ArkUI 动画开发概述
  • animateTo 显式动画详解
  • 鸿蒙开发者社区
  • HarmonyOS NEXT 应用开发指南
http://www.jsqmd.com/news/1160668/

相关文章:

  • Unity AR开发:华为手机Vuforia黑屏问题诊断与解决方案
  • 百度文库免费下载终极指南:5分钟掌握文档获取完整方案
  • 从零构建私有化AI应用:本地部署、RAG与微调实战指南
  • 解决Ubuntu重启后密钥环解锁弹窗:原理与三种修复方法
  • 专业级AMD Ryzen硬件调试工具:5大核心功能实战优化指南
  • TV Bro:重新定义智能电视网页浏览体验的遥控器专属浏览器
  • SNI明文传输的“裸奔“时代即将终结——ECH加密握手详解
  • 如何快速获取网盘直链:八大平台一键解析下载地址指南
  • 实战指南:如何高效使用Steam-auto-crack开源工具的5个关键步骤
  • 2026快速审批汽车分期信用卡哪家好?行业合规机构盘点、实用选型逻辑 + 合作签约避坑全攻略 - U渠道
  • 2026 国内主流靠谱 GEO 优化服务商深度实力盘点|五大标杆企业全维度实测解析 - AI营销社|GEO测评
  • 2026 年 7 月惠安黄金回收全攻略|螺城 / 崇武 / 台商区靠谱门店实测,卖金不踩坑 - 华金汇黄金回收
  • Android 14 适配实战:3大隐私变更与5个关键API适配代码示例
  • 3步解锁Steam动态壁纸:告别付费限制的终极解决方案
  • 三步解决魔兽争霸III兼容性问题:WarcraftHelper开源工具完整指南
  • 毕业设计项目 大数据共享单车数据分析与可视化(源码分享)
  • TrollInstallerDark:专为iOS 15.7.2–15.8.x arm64设备设计的无越狱安装方案
  • 2026成都奢侈品回收“防坑清单”出炉:六家门店实测,这些服务细节决定到手价! - 奢侈品回收中心
  • 守门员的眼睛:LLVM/MLIR 自动化测试神器 FileCheck 深度洗劫
  • 2026年广州食品经营许可证代办靠谱机构推荐与避坑指南 - 电商财税专家
  • Android 14 应用 UID 获取全解析:4种代码方法与2种Shell命令实战
  • 积家中国官方售后服务中心|官网认证网点地址及电话权威公告(2026年7月最新) - 积家中国服务中心
  • OpenClaw生产部署安全加固四层防护体系
  • 真实有效的免费降英文AI工具服务商
  • 稳压二极管 3 个易忽略参数解析:动态电阻 ZZT、漏电流 IR 与拐点电流 IZK 对电路稳定性的影响
  • 理查德米勒变现认准哈尔滨实体老店,门店覆盖全城,隐私保密无任何杂费 - 合扬
  • 室内运动木地板选择攻略:如何辨识诚信厂家?
  • Mimo Code SSH连接原理与排错实战指南
  • 3步解锁GitHub极速访问:智能DNS解析让你的开发效率提升300%
  • 专业开源项目管理平台:企业级团队协作的完整解决方案