一. 动画API认识
动画其实是我们通过某些方式(比如对象,Animation对象)给Flutter引擎提供不同的值,而Flutter可以根据我们提供的值,给对应的Widget添加顺滑的动画效果。
1.1. Animation
在Flutter中,实现动画的核心类是Animation,Widget可以直接将这些动画合并到自己的build方法中来读取当前值或者监听状态变化。
Animation抽象类:
- addListener方法
- 每当动画的状态值发生变化时,动画都会通知所有通过 addListener 添加的监听器。
- 通常,一个正在监听动画的state对象会调用自身的setState方法,将自身传入这些监听器的回调函数来通知 widget 系统需要根据新状态值进行重新构建。
addStatusListener
- 当动画的状态发生变化时,会通知所有通过 addStatusListener 添加的监听器。
- 通常情况下,动画会从 dismissed 状态开始,表示它处于变化区间的开始点。
- 举例来说,从 0.0 到 1.0 的动画在 dismissed 状态时的值应该是 0.0。
- 动画进行的下一状态可能是 forward(比如从 0.0 到 1.0)或者 reverse(比如从 1.0 到 0.0)。
- 最终,如果动画到达其区间的结束点(比如 1.0),则动画会变成 completed 状态。
```dart
abstractclass Animation
extends Listenable implements ValueListenable { const Animation();
// 添加动画监听器 @override void addListener(VoidCallback listener);
// 移除动画监听器 @override void removeListener(VoidCallback listener);
// 添加动画状态监听器 void addStatusListener(AnimationStatusListener listener);
// 移除动画状态监听器 void removeStatusListener(AnimationStatusListener listener);
// 获取动画当前状态 AnimationStatus get status;
// 获取动画当前的值 @override T get value; ```
1.2. AnimationController
Animation抽象类,并不能用来直接创建对象实现动画的使用。
AnimationController是Animation的一个子类,实现动画通常我们需要创建AnimationController对象。AnimationController会生成一系列的值,默认情况下值是0.0到1.0区间的值;
除了上面的监听,获取动画的状态、值之外,AnimationController还提供了对动画的控制:
- forward:向前执行动画
- reverse:方向播放动画
- stop:停止动画
AnimationController的源码:
class AnimationController extends Animation<double>with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {AnimationController({// 初始化值double value,// 动画执行的时间this.duration,// 反向动画执行的时间this.reverseDuration,// 最小值this.lowerBound = 0.0,// 最大值this.upperBound = 1.0,// 刷新率ticker的回调(看下面详细解析)@required TickerProvider vsync,})}
AnimationController有一个必传的参数vsync,它是什么呢?
- Flutter的渲染闭环,Flutter每次渲染一帧画面之前都需要等待一个vsync信号;
- 这里是为了监听vsync信号,当Flutter开发的应用程序不再接受同步信号时(比如锁屏或退到后台),那么继续执行动画会消耗性能;
- 这个时候设置了Ticker,就不会再出发动画了;
开发中比较常见的是将SingleTickerProviderStateMixin混入到State的定义中;
1.3. CurvedAnimation
CurvedAnimation也是Animation的一个实现类,目的是为了给AnimationController增加动画曲线:
CurvedAnimation可以将AnimationController和Curve结合起来,生成一个新的Animation对象
class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> {CurvedAnimation({// 通常传入一个AnimationController@requiredthis.parent,// Curve类型的对象@requiredthis.curve,this.reverseCurve,});}
Curve类型的对象的有一些常量Curves(和Color类型有一些Colors是一样的),可以供我们直接使用:
示例:
import'dart:math';class ShakeCurve extends Curve {@overridedouble transform(double t) => sin(t * pi * 2);}
1.4. Tween
默认情况下,AnimationController动画生成的值所在区间是0.0到1.0
- 如果希望使用这个以外的值,或者其他的数据类型,就需要使用Tween
源码:
class Tween<T extends dynamic> extends Animatable<T> {Tween({ this.begin, this.end });}
Tween也有一些子类,比如ColorTween、BorderTween,可以针对动画或者边框来设置动画的值。
Tween.animate
要使用Tween对象,需要调用Tween的animate()方法,传入一个Animation对象。
二. 动画案例练习
2.1. 动画的基本使用
心跳动画 ```dart class HYHomePage extends StatelessWidget { final GlobalKey<_AnimationDemo01State> demo01Key = GlobalKey();
@override Widget build(BuildContext context) { return Scaffold(
appBar: AppBar(title: Text("动画测试"),),body: AnimationDemo01(key: demo01Key),floatingActionButton: FloatingActionButton(child: Icon(Icons.play_circle_filled),onPressed: () {if (!demo01Key.currentState.controller.isAnimating) {demo01Key.currentState.controller.forward();} else {demo01Key.currentState.controller.stop();}},),
); } }
class AnimationDemo01 extends StatefulWidget { AnimationDemo01({Key key}): super(key: key);
@override _AnimationDemo01State createState() => _AnimationDemo01State(); }
class _AnimationDemo01State extends State
@override void initState() { super.initState();
// 1.创建AnimationControllercontroller = AnimationController(duration: Duration(seconds: 1), vsync: this);// 2.动画添加Curve效果animation = CurvedAnimation(parent: controller, curve: Curves.elasticInOut, reverseCurve: Curves.easeOut);// 3.监听动画animation.addListener(() {setState(() {});});// 4.控制动画的翻转animation.addStatusListener((status) {if (status == AnimationStatus.completed) {controller.reverse();} elseif (status == AnimationStatus.dismissed) {controller.forward();}});// 5.设置值的范围animation = Tween(begin: 50.0, end: 120.0).animate(controller);
}
@override Widget build(BuildContext context) { return Center( child: Icon(Icons.favorite, color: Colors.red, size: animation.value,), ); }
@override void dispose() { controller.dispose(); super.dispose(); } }
<a name="Erm4s"></a>### 2.2. AnimatedWidget上面代码,必须监听动画值的改变,并且改变后需要调用setState,这会带来问题:- 执行动画必须包含这部分代码,代码冗余- 调用setState意味着整个State类中的build方法就会被重新build可使用AnimatedWidget优化:```dartclass IconAnimation extends AnimatedWidget {IconAnimation(Animation animation): super(listenable: animation);@overrideWidget build(BuildContext context) {Animation animation = listenable;return Icon(Icons.favorite, color: Colors.red, size: animation.value,);}}修改_AnimationDemo01State中的代码:class _AnimationDemo01State extends State<AnimationDemo01> with SingleTickerProviderStateMixin {AnimationController controller;Animation<double> animation;@overridevoid initState() {super.initState();// 1.创建AnimationControllercontroller = AnimationController(duration: Duration(seconds: 1), vsync: this);// 2.动画添加Curve效果animation = CurvedAnimation(parent: controller, curve: Curves.elasticInOut, reverseCurve: Curves.easeOut);// 3.监听动画// 4.控制动画的翻转animation.addStatusListener((status) {if (status == AnimationStatus.completed) {controller.reverse();} elseif (status == AnimationStatus.dismissed) {controller.forward();}});// 5.设置值的范围animation = Tween(begin: 50.0, end: 120.0).animate(controller);}@overrideWidget build(BuildContext context) {return Center(child: IconAnimation(animation),);}@overridevoid dispose() {controller.dispose();super.dispose();}}
2.3. AnimatedBuilder
Animated是不是最佳的解决方案呢?
- 每次都要新建一个类来继承自AnimatedWidget
- 如果动画Widget有子Widget,那么意味着它的子Widget也会重新build
使用AnimatedBuilder优化:
class _AnimationDemo01State extends State<AnimationDemo01> with SingleTickerProviderStateMixin {AnimationController controller;Animation<double> animation;@overridevoid initState() {super.initState();// 1.创建AnimationControllercontroller = AnimationController(duration: Duration(seconds: 1), vsync: this);// 2.动画添加Curve效果animation = CurvedAnimation(parent: controller, curve: Curves.elasticInOut, reverseCurve: Curves.easeOut);// 3.监听动画// 4.控制动画的翻转animation.addStatusListener((status) {if (status == AnimationStatus.completed) {controller.reverse();} elseif (status == AnimationStatus.dismissed) {controller.forward();}});// 5.设置值的范围animation = Tween(begin: 50.0, end: 120.0).animate(controller);}@overrideWidget build(BuildContext context) {return Center(child: AnimatedBuilder(animation: animation,builder: (ctx, child) {return Icon(Icons.favorite, color: Colors.red, size: animation.value,);},),);}@overridevoid dispose() {controller.dispose();super.dispose();}}
三. 其它动画补充
3.1. 交织动画
3.2. Hero动画
需求:
- 点击一个商品的图片,展示商品的大图,并且从原来图像的Rect到大图的Rect
这种跨页面共享的动画被称之为享元动画(Shared Element Transition)
在Flutter中,有一个专门的Widget可以来实现这种动画效果:Hero
步骤:
- Page1中,定义一个起始的Hero Widget,被称之为source hero,并且绑定一个tag;
- Page2中,定义一个终点的Hero Widget,被称之为 destination hero,并且绑定相同的tag;
- 3.可以通过Navigator来实现第一个页面Page1到第二个页面Page2的跳转过程;
