第30次:特效与动画

本文不讲抽象动画理论,而是直接结合项目现有实现来看。当前项目中最有代表性的动画和特效主要集中在 Index.ets 的新年烟花效果,以及 FloatingButton.ets 中的浮动菜单展开动画。


为什么这个项目里的动画值得单独讲

很多教程会把动画写成和业务完全脱节的“花活”,但当前项目里的动画至少有两个很好的特点:

  • 动画是附着在真实业务页面上的
  • 动画有明确的触发条件和退出机制

这就说明它不是“为了炫技而炫技”,而是在增强节日氛围、提升交互反馈。

Index.aboutToAppear

checkNewYearPeriod

showFireworks = true

startFireworks

launchFirework 多次发射粒子

setInterval 持续 updateParticles

5秒后 fadeOutFireworks

动画结束并清理定时器


一、节日烟花效果是怎么做出来的

Index.ets 里实现了一个完整的烟花粒子系统。核心思路不是直接播放视频,而是自己维护粒子状态:

  • 粒子位置 x / y
  • 速度 vx / vy
  • 颜色
  • 大小
  • 透明度
  • 生命周期

然后通过定时器不断刷新这些状态,让粒子看起来像爆炸、扩散和下落。

这种做法的好处是:

  • 动画完全可控
  • 可以随机生成,不会死板
  • 不依赖额外视频资源

对于鸿蒙声明式 UI 来说,这是一种很有教学价值的动画实现方式。


二、为什么烟花动画要有“触发条件”

动画不是一进应用就无脑播放,而是先调用 checkNewYearPeriod(),只在设定时间范围内开启。也就是说,这个动画本质上是一个“节日主题活动层”,不是常驻效果。

这种设计非常成熟,因为它说明作者考虑了:

  • 动画的出现时机
  • 动画对主界面的影响
  • 动画结束后的清理逻辑

真正好的动画,永远不是到处乱飞,而是知道什么时候该出现、什么时候该收回。


三、为什么 FireworksOverlay() 要单独作为一个层

烟花效果并没有塞进首页内容区,而是放在最外层 Stack() 中,作为一个覆盖层出现。这样做有几个明显好处:

  1. 不会破坏原本页面布局。
  2. 可以在任意时刻整体显示或隐藏。
  3. 便于统一控制透明度和点击关闭。

这是一种很典型的“视觉特效层”设计方式。你以后做欢迎动画、全局提示、节日皮肤、庆祝弹层时,都可以参考这种写法。


四、为什么动画一定要处理退出和清理

当前代码里有几个很重要的清理动作:

  • aboutToDisappear() 中停止定时器
  • fadeOutFireworks() 中逐步降低透明度
  • dismissFireworks() 中主动关闭并清空粒子数组

这说明动画不是只管开始,不管结束。尤其在移动端,动画如果不处理好生命周期,就会带来:

  • 内存泄漏
  • 定时器残留
  • 页面切走后仍在消耗性能

所以教程里必须强调:动画系统一定要考虑“清理”,这和写业务逻辑一样重要。


五、浮动菜单动画为什么也是很好的示例

除了烟花效果,FloatingButton.ets 中的 FloatingActionMenu 也提供了一个很实用的小型动画案例。

它的核心思路是:

  • 主按钮点击后切换 isExpanded
  • 根据状态决定是否显示搜索和收藏入口
  • 展开的子按钮分别加了不同 delay
  • 使用 Curve.EaseOut 提升展开观感

这虽然只是一个简单菜单,但它很好地体现了“动画服务于交互反馈”的原则。用户点了主按钮,就应该明显感觉到界面进入了新状态,而不是突然多出几个按钮。


六、动画与业务边界应该怎么拿捏

这一篇最值得你记住的一点是:动画要增强业务,而不是抢业务的戏。

在当前项目里:

  • 烟花动画只在节日时触发,并且可点击关闭
  • 浮动菜单动画只是辅助入口展开
  • 技能树节点、卡片等视觉状态也只是轻量强化

这说明动画设计是克制的。移动端学习应用尤其需要这一点,因为用户的核心目标是学习内容,不是看特效。


七、图文结合看一张动画层示意图

业务内容层 Tabs / 首页 / 列表

覆盖层 Stack

FireworksOverlay

半透明背景

粒子 Circle 列表

节日文案

这张图能帮助你理解:真正稳定的动画,往往不是直接改业务层,而是额外叠一层视觉层。


八、自己动手时怎么验证最有效

  1. 打开 Index.ets,先看烟花相关状态定义。
  2. 顺着 checkNewYearPeriod -> startFireworks -> updateParticles -> fadeOutFireworks 读一遍。
  3. 再看 FireworksOverlay() 如何把粒子渲染成多个 Circle()
  4. 打开 FloatingButton.ets,观察展开菜单的动画写法。
  5. 思考如果你要把动画移除,哪些业务仍然能正常工作。

做到这里,你对动画与业务之间的边界就会更清晰。


九、本篇常见坑

1. 动画没有退出逻辑

这是最常见也最危险的问题。

2. 动画直接写进业务布局

覆盖层设计通常更安全、更清晰。

3. 动画太重,影响主要流程

学习应用尤其要避免喧宾夺主。

4. 所有按钮同时弹出,没有层次

像浮动菜单那样加轻微延迟,体验会更自然。


本篇小结

当前项目里的特效与动画给了我们两个很实用的范例:

  • 大型节日视觉层:烟花粒子动画
  • 小型交互反馈层:浮动菜单展开动画

两者共同说明了一件事:动画不是孤立功能,而是服务于产品氛围和操作反馈的辅助手段。


跟着真实源码继续往下看

烟花动画发射粒子的真实代码如下:

private launchFirework(x: number, y: number): void {
  const colors = ['#ff6b6b', '#ffd93d', '#6bcb77', '#4d96ff', '#ff6bd6', '#61DAFB'];
  const particleCount = 30;

  for (let i = 0; i < particleCount; i++) {
    const angle = (Math.PI * 2 * i) / particleCount + Math.random() * 0.5;
    const speed = 3 + Math.random() * 4;

浮动菜单展开动画的真实代码如下:

if (this.isExpanded) {
  Column() {
    FloatingButton({
      icon: '🔍',
      size: 48,
      onTap: () => {
        this.isExpanded = false;
        this.onSearchTap();
      }
    })
  }
  .animation({
    duration: 200,
    curve: Curve.EaseOut
  })
}

按这个顺序动手

  1. 打开 Index.ets,搜索 launchFireworkupdateParticles
  2. 再打开 FloatingButton.ets,看 isExpanded 如何控制菜单展开。
  3. 观察动画状态和业务状态是不是清楚分开了。

课后练习

  1. 想一想如果要把烟花效果改成“完成模块时触发一次庆祝动画”,最适合由哪个页面触发。
  2. 观察 FloatingActionMenu 的延迟配置,思考为什么两个按钮不同时出现更自然。
  3. 说明为什么动画清理逻辑应该被视为功能的一部分,而不是附属细节。
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐