HarmonyOS 6.1 开发者盛宴|《灵犀厨房》实战(二十三):【交互动效】转场、列表动画与趣味反馈——让每一次点击都有温度
HarmonyOS 6.1 开发者盛宴|《灵犀厨房》实战(二十三):【交互动效】转场、列表动画与趣味反馈——让每一次点击都有温度
摘要:前面 22 篇我们完成了《灵犀厨房》的核心功能——推荐、菜谱、流转、语音、视频。但功能齐全 ≠ 体验优秀。用户在第一秒就会用「眼睛」和「手指」投票:页面切换卡不卡?列表加载生不生硬?按钮有没有反馈?本篇将用 HarmonyOS 6.1.0 的 **属性动画(.animation)**和变换(.scale / .translate / .opacity),为《灵犀厨房》注入「灵气」——让页面有呼吸感、让列表有节奏感、让每一次点击都有温度。我们不增加任何新功能,只改动约 80 行动效代码,让 App 从“能用”跃升到“好用”。
一、引言:功能齐全 ≠ 体验优秀
一个有趣的实验:把《灵犀厨房》第 22 篇的版本拿给一个没用过的人,让他从首页点进菜谱详情。他会顺利完成任务——但不会觉得“好用”。他会觉得页面“突然就出现了”,列表“一下子就全出来了”,按钮“点了好像没点”。
这不是 Bug,但比 Bug 更致命——用户不会抱怨,他们只会不再打开。
动效不是“炫技”。好的动效像空气——你感觉不到它的存在,但一旦缺失,一切都变得生硬。本篇要做的,就是用最少的代码,给《灵犀厨房》注入这种“空气感”。
图一解读:三个维度对应三个核心技法,每个技法服务一个明确的设计目标。转场动画用透明度+位移让元素“滑”进来而非“闪现”;列表动画用延迟制造节奏感,像“翻开书页”一样逐项展开;趣味反馈用缩放模拟物理按压,让屏幕上的按钮有了“弹性”。三者互不依赖,可以单独应用,也可以组合使用。
二、核心原理:ArkUI 属性动画的“三段式”模型
在进入具体实现之前,先理解 HarmonyOS 属性动画的工作原理:
初始状态 ──→ 触发条件(状态变量变化) ──→ 目标状态 │ └── .animation({ duration, curve, delay }) │ └── ArkUI 自动插值计算中间帧你只需要做两件事:
- 声明初始状态和目标状态(如
opacity: 0 → 1) - 告诉 ArkUI 变化发生时用什么曲线、多久完成(
.animation({...}))
中间的所有插值计算、帧渲染、硬件加速——ArkUI 全部自动完成。这就是声明式动画的核心优势:你描述“从哪到哪”,框架负责“怎么过去”。
2.1 动画曲线的选择
| 曲线 | 视觉效果 | 适用场景 | 本篇使用 |
|---|---|---|---|
Curve.EaseOut | 先快后慢,像“轻轻落下” | 入场动画、弹回动画 | ✅ |
Curve.EaseInOut | 两端慢中间快,像“呼吸” | 循环动画 | ✅ |
Curve.Linear | 匀速 | 进度条 | ❌(太机械) |
设计决策:为什么本篇几乎全部使用
Curve.EaseOut?因为自然界中没有“匀速运动”——物体总是先快后慢地停下。EaseOut模拟了这种物理惯性,让动画看起来“自然”而非“机械”。
2.2 动画触发的“黄金窗口”
所有入场动画都有一个微小的setTimeout延迟(80~150ms):
setTimeout(()=>{this.heroReady=true;},150);这不是为了“炫技”,而是为了避开首次布局的帧丢失。组件首次渲染时,ArkUI 需要完成布局计算、绘制、合成——如果在这期间触发动画,可能被系统跳帧,导致动画“凭空出现”而非“滑入”。150ms 的延迟确保布局完成后再启动动画,用户看到的是完整的入场过程。
三、实战一:转场动画——Hero 卡片上滑入场
首页的 AI 食材识别卡片是用户打开 App 第一眼看到的焦点区域。让它“滑”进来,比直接出现更有吸引力。
3.1 实现
@LocalheroReady:boolean=false;aboutToAppear():void{setTimeout(()=>{this.heroReady=true;},150);}buildHeroCard(){Column(){// ...卡片内容...}.opacity(this.heroReady?1:0).translate({y:this.heroReady?0:40}).animation({duration:500,curve:Curve.EaseOut})}3.2 设计决策:为什么是 40vp 和 500ms?
| 参数 | 选择 | 如果更大 | 如果更小 |
|---|---|---|---|
| 位移 40vp | 屏幕高度约 5% | 动画太“跳”,用户注意力被分散 | 几乎感觉不到滑入 |
| 时长 500ms | 刚好感知,不等待 | 用户觉得 App 慢 | 动画一闪而过 |
这是一个平衡点:足够让用户感知到“滑入”,但不至于让他们等待。Curve.EaseOut的减速收尾让卡片像“轻轻落下”而非“匀速滑入”,这是物理直觉在 UI 中的投射。
四、实战二:列表动画——购物清单错落入场
购物清单是分组列表。一次性全部显示会显得“生硬”;每一行错落 60ms 依次出现,则像“被轻轻翻开”。
4.1 核心实现
@LocallistReady:boolean=false;aboutToAppear():void{setTimeout(()=>{this.listReady=true;},80);}ListItem(){Row({space:10}){Circle().width(8).height(8).fill(categoryColor)Text(item).fontSize(15)}}.opacity(this.listReady?1:0).translate({x:this.listReady?0:-20}).animation({duration:350,curve:Curve.EaseOut,delay:80+itemIndex*60// ← 关键:延迟递增})4.2 品类分组的双层延迟
品类(ListItemGroup)也需要自己的入场节奏:
.opacity(this.listReady?1:0).animation({duration:400,curve:Curve.EaseOut,delay:100+categoryIndex*120// 品类间延迟 120ms})整体效果:
品类「肉类」 ← 延迟 100ms 出现 牛腩 500g ← 延迟 180ms 出现 排骨 500g ← 延迟 240ms 出现 品类「蔬菜」 ← 延迟 220ms 出现 番茄 3个 ← 延迟 300ms 出现4.3 趣味点缀:呼吸圆点
每个食材项末尾加了一个微小的彩色圆点,用PlayMode.Alternate做无限呼吸动画:
Circle().width(5).height(5).fill(categoryColor).opacity(0.35).animation({duration:2000+itemIndex*300,// 每项错开周期,避免同步闪烁curve:Curve.EaseInOut,iterations:-1,// 无限循环playMode:PlayMode.Alternate// 来回播放})设计考量:为什么每个圆点要错开 300ms 起始周期?如果所有圆点同时明灭,会产生“集体闪烁”的视觉疲劳。错开周期后,它们像各自在“呼吸”,整体视觉效果柔和且不死板。
五、实战三:趣味反馈——按压缩放与爆发动画
5.1 RecommendCard 按压缩放
用户手指按下卡片时,给出一个微小的“陷下去”的感觉——这是触觉反馈的视觉替代。
@StateisPressed:boolean=false;build(){Column(){/* ... */}.scale({x:this.isPressed?0.97:1,y:this.isPressed?0.97:1}).animation({duration:150,curve:Curve.EaseOut}).onTouch((event:TouchEvent)=>{if(event.type===TouchType.Down){this.isPressed=true;}elseif(event.type===TouchType.Up||event.type===TouchType.Cancel){this.isPressed=false;}})}为什么用onTouch而非onClick?
| 事件 | 触发时机 | 能否感知 Down |
|---|---|---|
onClick | 手指抬起时 | ❌ |
onTouch | Down / Up / Cancel 均可 | ✅ |
按压反馈的关键是“按下去的瞬间”——TouchType.Down。onClick只能感知抬起,无法实现这个效果。
5.2 收藏按钮爆发动画
@LocalheartScale:number=1;@LocalheartLiked:boolean=false;Button(){if(this.heartLiked){SymbolGlyph($r('sys.symbol.heart_fill')).fontSize(16).fontColor(['#FF2D55'])}else{SymbolGlyph($r('sys.symbol.heart')).fontSize(16).fontColor([Color.White])}}.scale({x:this.heartScale,y:this.heartScale}).animation({duration:300,curve:Curve.EaseOut}).onClick(()=>{this.heartLiked=!this.heartLiked;this.heartScale=1.3;// 瞬间放大到 130%setTimeout(()=>{this.heartScale=1;},150);// 150ms 后弹回})动画时序:
点击瞬间: scale 1→1.3, liked false→true 150ms 后: scale 1.3→1(弹回) 300ms 后: 动画完成整个爆发动画在 300ms 内完成——这是用户感知“反馈”的最佳窗口。再短则无感,再长则拖沓。
5.3 RecommendCard 错落入场
每张卡片按索引延迟触发入场,在双列瀑布流中自然形成“Z 字形”视觉流:
@PropitemIndex:number=0;@StatecardReady:boolean=false;aboutToAppear():void{setTimeout(()=>{this.cardReady=true;},80+this.itemIndex*70);}.opacity(this.cardReady?1:0).translate({y:this.cardReady?0:30}).animation({duration:400,curve:Curve.EaseOut,delay:this.itemIndex*70})六、视频进度 → 步骤同步(续上篇)
在上篇 AVPlayer 集成的基础上,新增syncStepWithVideoProgress()方法,使视频播放进度与下半部步骤展示保持同步:
privatesyncStepWithVideoProgress():void{if(this.videoDuration<=0||this.recipe.steps.length<=1)return;constprogress:number=this.videoCurrentTime/this.videoDuration;constnewStepIndex:number=Math.min(Math.floor(progress*this.recipe.steps.length),this.recipe.steps.length-1);if(newStepIndex!==this.currentStepIndex){this.currentStepIndex=newStepIndex;}}生产环境中,可由后端返回每步的精确时间戳,替换均分逻辑。
七、代码增删改清单
| 文件 | 新增/修改 | 动效变更 |
|---|---|---|
pages/Index.ets | 修改 | Hero 卡片上滑入场 |
components/RecommendCard.ets | 重大修改 | 新增isPressed按压缩放 97%、错落入场上滑、接受itemIndex属性 |
pages/ShoppingListPage.ets | 重构 | 错落入场(双层延迟)、呼吸圆点、品类逐级出现 |
pages/RecipeDetailPage.ets | 修改 | 收藏按钮爆发动画、视频进度→步骤同步 |
八、设计决策
| 决策 | 选择 | 理由 |
|---|---|---|
| 入场动画触发时机 | setTimeout(80~150ms) | 等待首次布局完成,避免动画在帧丢失中被吞掉 |
| 缩放比例 | 97%(按下)/ 130%(爆发) | 97% 微妙但可感知;130% 明显但不溢出 |
| 列表错落延迟 | 60ms/项 | 10 项列表 ≈ 600ms 完成,用户不等待 |
| 呼吸动画 | PlayMode.Alternate+ 错开周期 | 避免所有圆点同步闪烁,减少视觉疲劳 |
| 按压反馈 API | onTouch而非onClick | onClick只有 Up 事件,无法感知 Down 的按压瞬间 |
| 动画曲线 | Curve.EaseOut | 先快后慢的减速曲线模拟物理惯性,比线性更自然 |
九、本阶段总结与下篇预告
本篇是《灵犀厨房》系列中“投入最少、感知最强”的一篇。我们没有新增任何功能,但用约 80 行动效代码,让整个 App 从“能用”跃升到“好用”:
- 转场动画:Hero 卡片和推荐瀑布流有了“呼吸感”——不是凭空出现,而是“滑”进来的
- 列表动画:购物清单从生硬的静态列表变成了有节奏的“翻开”效果
- 趣味反馈:收藏按钮的“啵”一下、卡片的“陷进去”,让每次点击都有了温度
好的动效不是“炫技”,而是让用户感觉不到技术在运作——一切如此自然,就像 App 本来就该这样。
下篇预告:第 24 篇《手势操作:滑动调整“火力大小”》。我们要让用户在智慧厨电模拟器上,像拧燃气灶旋钮一样,用手指滑动来调节火力。这是动效与交互的结合——手势驱动的动画,比定时器驱动的动画更贴近直觉。
📚 本系列持续更新中:下一篇《手势操作-滑动调整火力大小》将为 App 注入体验控制快感,让交互如丝般顺滑。
🔗专栏入口:[《HarmonyOS6.1全场景实战》合集]
📦 获取基线版本源码包:包括第1-15篇所有代码 + 架构文档 + Flask 后端
如果你觉得这篇文章对您有所帮助,麻烦您动动发财之手点赞 👍、收藏 ⭐ 和评论 💬。谢谢大家!!
纯血鸿蒙,用心造厨。我们下一篇见!
