Flutter:这个插件包会让你从此爱上动画

在 Flutter 中做动画并不容易。方法五花八门,插件也多如牛毛。我试着对这个课题进行了系统化梳理,并写了几篇文章。
从简单易用到灵活复杂,Flutter 动画可以划分为以下几个层级:
-
• AnimatedX 系列组件,以及 animate_do和flutter_animate插件[1] -
• TweenAnimationBuilder(补间动画构建器)[2] -
• 使用 AnimationController的显式动画[3]
现在出现了一个名为 Cue[4] 的新包,它将取代上述所有方案。
以下是该插件页面上一些精美的示例:



示例看起来不错,但你可能还是会问:这个插件到底有什么特别之处?
首先,它是“物理优先(Physics-first)”的;它默认使用弹簧(Springs)而非曲线(Curves),因此动画看起来更加自然。
但最重要的一点是,它改变了我们在页面上构建组件动画的思维方式。
Cue 并不是让你一个接一个地为组件添加动画,而是强制我们将整个屏幕(或其一部分)视为一个完整的“动画舞台”。

为了理解上述理念,我们需要一个实例。
我们可以看到,当页面加载时,有三个文本组件(Text Widgets)触发了动画。
代码如下:
Cue.onMount( motion: CueMotion.smooth(), child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, spacing: 100, children: [ Actor( acts: [ Act.fadeIn(from: 0.0), ScaleAct.keyframed( frames: Keyframes([ .key(0.92), .key(1.6, motion: .bouncy()), .key(1.0), ], motion: .smooth()), ), ], delay: Duration(milliseconds: 300), child: EzText('Child of Actor #1', fontSize: 22), ), Actor( acts: [Act.fadeIn(from: 0.0), Act.slideX(from: -5, to: 0.0)], delay: Duration(milliseconds: 600), child: EzText('Child of Actor #2', fontSize: 22), ), Actor( acts: [Act.fadeIn(from: 0.0), Act.slideY(from: 5, to: 0.0)], delay: Duration(milliseconds: 900), child: EzText('Child of Actor #3', fontSize: 22), ), ], ), ),
动画是由两个组件协同完成的:Cue 和 Actor。
Cue 负责定义触发动画的事件以及运动的类型:
Cue.onMount( motion: CueMotion.smooth(), ...
有很多事件可供选择:onMount(挂载时)、onToggle(切换时)、onChange(改变时)等等(详情请查阅文档[5])。
CueMotion 类允许你从多种预定义的运动类型中进行选择:smooth(平滑)、snappy(干脆)、interactive(交互式)等。
Actor 负责包装需要执行动画的组件。Actor 必须始终位于 Cue(动画场景)内部。此外,Actor 还可以嵌套使用。
Actor( acts: [Act.fadeIn(from: 0.0), Act.slideY(from: 5, to: 0.0)], delay: Duration(milliseconds: 900), child: EzText('Child of Actor #3', fontSize: 22), ),
(EzText[6] 只是我自己封装的文本组件。)
Act 类提供了一系列效果集,例如:scale(缩放)、fadeIn(淡入)、slideX(水平位移)等。
让我们再次回顾一下这个示例 GIF:

该示例的源代码具有以下结构:
Cue.onToggle( //定义触发方式与运动类型 ... Actor ( //外层 Actor:负责整个卡片的动画效果。 ... Actor ( //内层 Actor:负责标题的动画效果。 ... Actor ( // 另一个内层 Actor:负责正文内容的动画效果。...
这段动画并不是通过给不同的组件分别施加独立的效果而创建的;它在某种程度上是作为一个整体被设计出来的。我认为这带来了巨大的差异。
那我们可以用 Cue 来为单个组件制作动画吗?当然可以。在这种情况下,我们可以省略 Actor,让代码变得更加简洁:
Cue.onMount( motion: .smooth(), acts: [ .fadeIn(), .slideY(from: 0.2), ], child: const Text('Hello Cue'),)
注意这种简写语法。
关键帧(Keyframes)
这是我第一个示例中的代码:
Actor( acts: [ ScaleAct.keyframed( frames: Keyframes([ Keyframe.key(0.92), .key(1.6, motion: .bouncy()), .key(1.0), ] child: ...
起初我对“关键帧(Keyframe)”这个词感到很困惑,不明白它的具体作用。实际上,它定义的是一种“多步动画”。
上面的代码定义了三个步骤:
-
1. 缩放比例从 1 降至 0.92 -
2. 从 0.92 升至 1.6 -
3. 再从 1.6 降回 1
在 Flutter 中,“frame(帧)”和“key(键/密钥)”这两个词都会引发强烈的特定联想。我认为如果将 Keyframe 替换为 Step(步骤),将 key 替换为 value(值),意思会更清晰。那么,上面的代码看起来就会像这样:
Actor( acts: [ ScaleAct.stepped( steps: Steps([ Step.value(0.92), .value(1.6, motion: .bouncy()), .value(1.0), ] child: ...
不过我这纯属是在挑刺啦。😎
智能体技能(Agentic SKILL)这个插件的 API 并不简单,确实存在一定的学习曲线。但借助“Skills[7]”,我们现在就能上手。
事实上,那个示例代码并不是我亲手写的。倒不是因为看起来很难,纯粹是为了做个实验。首先,我创建了一个看起来长这样的页面:

起初只是一个包含三个 Text 组件的简单 Column。
然后我要求智能体(Agent)使用 cue-animations 技能来为它制作动画。生成的代码正确率大约在 97% 左右。完全可行。
结语
Cue 是一个非常强大的动画插件包,它在设计复杂动画方面有着独特的方法论。
感谢阅读,祝你动画创作愉快!
往期阅读:
AnimatedCount:一款用于实现数字平滑动画的轻量级 Flutter 软件包
Flutter 3.38 动画新特性:动画引擎有什么新变化?
Flutter:我在网上看到了一个超炫的动画边框,于是我在 Flutter 里把它实现了出来
Flutter动画之Flare的制作与使用
Flutter 自定义 CustomPaint 实现流体液态加载动画
引用链接
[1] AnimatedX 系列组件,以及 `animate_do` 和 `flutter_animate` 插件: https://medium.com/easy-flutter/flutter-implicit-animations-with-and-without-packages-9292e0ba0330[2] `TweenAnimationBuilder`(补间动画构建器): https://medium.com/easy-flutter/flutter-implicit-animations-with-tweenanimationbuilder-1397c87b9e6e[3] 使用 `AnimationController` 的显式动画: https://medium.com/easy-flutter/flutter-explicit-animations-curves-vs-physics-vs-springs-41100beb8cb4[4] Cue: https://pub.dev/packages/cue[5] 文档: https://pub.dev/packages/cue#1-cue-the-trigger[6] `EzText`: https://medium.com/easy-flutter/flutter-my-new-widget-eztext-and-more-756104addb3c[7] Skills: https://github.com/Milad-Akarie/cue/blob/main/.github/skills/cue-animations/SKILL.md
夜雨聆风