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 Propsubtitle PropButtonType.Capsule呼吸脉冲 setIntervalonCtaClick 回调生命周期aboutToAppear 触发入场动画aboutToDisappear 清除定时器一、空状态组件的设计原则1.1 三个核心要素一个完整的空状态应包含以下三个要素要素作用EmptyState 实现插画/图标视觉提示唤起情感共鸣手绘风纸笔插画Stack 模拟提示文字说明当前状态titlesubtitle双行文字行动号召CTA引导用户操作胶囊按钮 呼吸脉冲动画Componentexportstruct EmptyState{// 三要素Proptitle:string还没有分析记录快来试试吧// 提示文字Propsubtitle:string上传一段手写笔记 · 让 AI 读懂你PropctaText:string拍下第一笔// CTA 按钮文字onCtaClick?:()void// CTA 回调}1.2 UI 2.0 中的空状态规范鸿蒙设计规范推荐的空状态原则友善而非冷漠使用插画而非纯文字引导而非指责“还没有内容” “内容为空”单一 CTA只提供一个主要操作入口动态而非静止加入细微动画提升浏览体验提示空状态中的插画应保持品牌一致性——色彩、风格、语气都需要与 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%设计细节rotate({ angle: -3 })— 纸张轻微旋转打破太整齐的机械感三道波浪线长度递减4/3/2 个波浪符透明度递减羽毛笔position({ x: 110, y: -10 })超出 Stack 边界增加趣味性三、入场动画实现3.1 aboutToAppear 生命周期空状态的入场动画在aboutToAppear中触发——这是组件创建后、首次渲染前调用的生命周期方法// 入场动画状态StateillustrationOpacity:number0// 插画透明度从 0 到 1StateillustrationOffset:number20// 插画 Y 偏移从 20 到 0aboutToAppear():void{// 插画淡入 上移animateTo({duration:600,curve:Curve.FastOutSlowIn},(){this.illustrationOpacity1// 透明度 0→1this.illustrationOffset0// 偏移 20→0})// 启动呼吸脉冲定时器this.pulseTimersetInterval((){animateTo({duration:800,curve:Curve.EaseOut},(){this.pulseScalethis.pulseScale1.05?1:1.06})},1600)}动画效果插画从下方 20vp 处淡入上移600msCTA 按钮开始循环呼吸缩放幅度 1.0↔1.06周期 1600ms文字区无动画自然渲染提示入场动画的 duration600ms略短于呼吸脉冲的周期1600ms确保入场完成后才开始呼吸避免动画重叠。四、呼吸脉冲动画4.1 setInterval animateTo 循环脉冲动画使用setInterval定时器 animateTo显式动画的递归模式privatepulseTimer:number-1StatepulseScale:number1aboutToAppear():void{this.pulseTimersetInterval((){animateTo({duration:800,curve:Curve.EaseOut},(){this.pulseScalethis.pulseScale1.05?1:1.06})},1600)// 每 1.6 秒触发一次脉冲}时序分析时间点事件pulseScale按钮视觉T0ms组件创建1.0原始大小T1600ms第一次脉冲1.0 → 1.06800ms缓慢放大T2400ms第一次复位1.06 → 1.0800ms缓慢恢复T3200ms第二次脉冲1.0 → 1.06800ms再放大…持续循环…持续呼吸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.length0){EmptyState({title:还没有分析记录快来试试吧,ctaText:拍下第一笔,onCtaClick:(){this.getUIContext().getRouter().pushUrl({url:pages/CapturePage})}})}6.2 档案页空状态// ArchivePage.ets — 无档案时if(archives.length0){EmptyState({title:还没有创建任何档案,subtitle:分析完成后自动生成档案,ctaText:去分析,onCtaClick:(){/* 导航到分析页 */}})}提示同一个 EmptyState 组件通过不同的title/subtitle/ctaText组合可以覆盖无记录、“无档案”、无结果等多个空状态场景。七、空状态组件的可配置性7.1 可配置参数一览参数类型默认值场景覆盖titlestring“还没有分析记录”首页无数据subtitlestring“上传一段手写笔记”无资源ctaTextstring“拍下第一笔”操作引导onCtaClickcallbackundefined操作回调7.2 扩展建议空状态组件可以进一步扩展// 扩展版本未来可考虑Componentexportstruct EmptyState{Proptitle:stringPropsubtitle:stringPropctaText:stringPropillustrationType:IllustrationTypeIllustrationType.PAPER// 插画类型PropshowAnimation:booleantrue// 是否播放动画onCtaClick?:()void}八、EmptyState 的设计启示8.1 空状态的层次感一个好的空状态在视觉上应有明确的层次感视觉焦点插画区180×140vp占据中心位置信息传达文字区title subtitle居中排列行动引导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) 的完整代码出发深入解析了鸿蒙空状态组件的设计方法三要素结构插画 文字 CTA 按钮覆盖空状态的核心需求入场动画aboutToAppear中触发淡入上移动画600ms 自然过渡呼吸脉冲setIntervalanimateTo实现循环缩放1700ms 周期定时器管理aboutToDisappear中清理定时器防止内存泄漏回调模式CTA 操作通过onCtaClick回调交由父组件处理可配置性通过多个 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 应用开发指南