注:本文代码基于Flutter SDK 3.13.5

一、前言

如果你开发的App失去了交互动画,那么这将是一件很难想象的事情,此时它的体验必然一言难尽,用户会为它贴上“傻快”、“生硬”、“粗糙”等标签,从而你将不得不丢失一部分用户。

因此,精心设计的动画会使UI更生动,它有助于提升App更精巧的外观和感觉,从而改善用户体验。

FlutterMaterial Widgets中,这些Widgets均自带其设计规范中定义的标准动画效果,能让各种动画效果的实现变得容易,当然了,你也可以定制这些效果。

二、动画概览

Flutter中,创建动画可以有多种不同实现方式。那么,究竟哪种才是最适合你的呢?可以参考下面的决策树,它将帮助你挑选实现Flutter动画的正确方式。

2.1、动画的实现方式

按照动画的实现方式来分类,可以分为隐式动画与显式动画。

  • 隐式动画

所有隐式动画均扩展了ImplicitlyAnimatedWidget类。你可以使用内置的隐式动画,例如AnimatedContainerAnimatedAlign等,这些是最简单的动画。如果内置的隐式动画不能够满足你的需求,你也可以使用TweenAnimationBuilder创建一个自定义的隐式动画。

  • 显式动画

所谓显式动画,就是需要手动控制,而不是让框架控制。你可以使用内置的其中一个显式动画类来实现,例如ScaleTransitionRotationTransition等。如果内置的显式动画不能够满足你的需求,你也可以使用AnimatedBuilderAnimatedWidget创建一个自定义的显式动画。

当然了,不管隐式动画还是显式动画,它们也只是对底层Animation的一个封装,只是封装后的使用自由度不太相同罢了。

2.2、动画的类型

按照动画的类型来分类,可以分为补间动画与基于物理动画。

  • 补间动画

补间动画是“介于两者之间”的缩写。在补间动画中,定义了起点和终点以及时间轴,再定义过渡时间和速度的曲线,然后框架会计算如何从起点过渡到终点。

  • 基于物理动画

在基于物理基础的动画中,动作是模拟真实世界的行为来进行建模的。举个例子,当您抛球时,球落地的时间和位置取决于抛出的速度和距离地面的高度。类似地,附在弹簧上的球和附在绳子上的球掉落(和反弹)方式是不一样的。

2.3、预置动画

预置动画指的是pub.dev上的animations库中所包含的动画,它是由Flutter官方提供的。这个库包含了以下内置常用模式: Container变换、共享轴变化、渐变穿透和渐变变换。

2.4、常见的动画模式

常见的动画模式包括列表或网格动画、共享元素转换、交织动画等。

  • 列表或网格动画

这种模式用于在列表或网格中添加或删除元素。

  • 共享元素转换

在这个模式中,用户从页面中选择一个元素,通常是图像,然后UI会在新页面中为指定元素添加动画,并生成更多细节。在Flutter中,您可以通过Hero组件轻松实现路径(页面)间的共享元素转换动画。

例如,下面这两种风格的Hero动画:

1、当改变位置和大小时,Hero从一页飞至另一页。

2、Hero的边界改变形状由圆变方,同时从一页飞至另一页。

为什么要将这种可飞行的共享组件称为Hero(英雄),有一种说法是说美国文化中的超人是可以飞的,那是美国人心中的大英雄,还有漫威中的超级英雄基本上都是会飞的,所以Flutter开发人员就对这种“会飞的Widget”就起了一个富有浪漫主义的名字Hero。当然这种说法并非官方解释,但却很有意思。

  • 交织动画

将复杂的动画分解成较小的动作,其中一些动作被延迟,分解后的这些小动画可以是连续的,也可以部分或完全重叠。例如:有一个柱状图,需要在高度增长的同时改变颜色,等到增长到最大高度后,我们需要在X轴上平移一段距离。

使用交织动画需要注意以下几点:

1、要创建交织动画,需要使用多个动画对象(Animation

2、一个AnimationController控制所有的动画对象

3、给每一个动画对象指定时间间隔(Interval

OK,动画总览讲解完毕,最后再给出一张动画总览的思维导图。

三、动画基础

在任何系统的UI框架中,动画的实现原理都是相同的,即在一段时间内,快速地多次改变UI外观;由于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画,这和电影的原理是一样的。

Flutter中对动画进行了抽象,主要涉及AnimationCurveControllerTween这四个角色,它们一起配合来完成一个完整动画,下面来一一介绍它们。

3.1、Animation

1、在Flutter中,动画对象Animation无法获取屏幕上显示的内容,它与渲染或 build() 方法无关。

2、Animation是一个已知当前值value和状态status(已完成或已解除)的抽象类,其中一个比较常见的Animation类型是 Animation<double>

3、动画还可以插入除double以外的类型,比如Animation<Color>或者Animation<Size>

4、一个Animation对象在一段时间内,持续生成介于两个值之间的插入值。这个Animation对象输出的可能是直线,曲线,阶梯函数,或者任何自定义的映射,这由Curve来决定。

5、根据Animation对象的不同控制方式,它可以反向运行,或者中途切换方向。

6、在动画的每一帧中,我们可以通过Animation对象的value属性获取动画的当前值。

3.1.1、动画通知

一个Animation对象可以有不止一个ListenerStatusListener,用addListener()addStatusListener() 来定义。

  • 当动画值改变时调用ListenerListener最常用的操作是调用 setState() 进行重建。

  • 当一个动画开始,结束,前进或后退时,会调用StatusListener,用AnimationStatus来定义。

3.2、Curve

CurvedAnimation定义动画进程为非线性曲线。

1
2
final CurvedAnimation curve =
    CurvedAnimation(parent: controller, curve: Curves.easeIn);

CurvedAnimationAnimationController(下面介绍)都是Animation<double>类型。CurvedAnimation可以通过包装AnimationControllerCurve生成一个新的动画对象,我们正是通过这种方式来将动画和动画执行的曲线关联起来的。

Curves类中定义了很多常用曲线。

Curves曲线 动画过程
linear 线性动画曲线
decelerate 变化率开始快速然后减速的曲线
ease 快速加速、缓慢结束的立方动画曲线
easeIn 缓慢开始并快速结束的立方动画曲线
easeOut 快速开始并缓慢结束的立方动画曲线
easeInOut 一条立方动画曲线,缓慢开始,加速,然后缓慢结束
bounceIn 幅度不断增长的振荡曲线
bounceOut 一条先增大后减小幅度的振荡曲线
bounceInOut 一条先增大后减小幅度的振荡曲线

如果内置的Curves曲线不能满足你的要求,你也可以自定义Curves曲线。例如:

1
2
3
4
5
6
import 'dart:math';

class ShakeCurve extends Curve {
  @override
  double transform(double t) => sin(t * pi * 2);
}

3.3、AnimationController

AnimationController是个特殊的Animation对象,每当硬件准备新帧时,它都会生成一个新值。默认情况下,AnimationController在给定期间内会线性生成从 0.0 到 1.0 的数字。例如,这段代码创建了一个动画对象,但是没有启动运行。

1
2
3
4
final AnimationController controller = AnimationController(
  duration: const Duration(milliseconds: 2000),
  vsync: this,
);

AnimationController生成数字的区间可以通过lowerBoundupperBound来指定,如:

1
2
3
4
5
6
final AnimationController controller = AnimationController( 
 duration: const Duration(milliseconds: 2000), 
 lowerBound: 10.0,
 upperBound: 20.0,
 vsync: this
);

AnimationController源自于Animation<double>,所以可以用在任何需要Animation对象的地方。但是AnimationController还有其它方法控制动画,例如:forward() 方法可以启动正向动画,reverse() 可以启动反向动画。

在动画开始执行后开始生成动画帧,屏幕每刷新一次就是一个动画帧,在动画的每一帧,会随着根据动画的曲线来生成当前的动画值(Animation.value),然后根据当前的动画值去构建UI,当所有动画帧依次触发时,动画值会依次改变,所以构建的UI也会依次变化,所以最终我们可以看到一个完成的动画。

创建AnimationController的同时,也赋予了一个vsync参数。vsync的存在防止后台动画消耗不必要的资源。您可以通过添加SingleTickerProviderStateMixin到类定义,将有状态的对象用作vsync

注意:在一些情况下,一个位置可能会超过AnimationController的 0.0-1.0 的范围。例如,fling() 函数可以根据我们手指滑动(甩出)的速度(velocity)、力量(force)等来模拟一个手指甩出动画,因此它的动画值可以在[0.0,1.0]范围之外。即使AnimationController在范围内,CurvedAnimation也可能会出现超出 0.0-1.0 范围的情况。根据所选曲线的不同,CurvedAnimation的输出范围可能会超过输入。举个例子,弹性曲线(比如Curves.elasticIn)会明显超出或低于默认范围。

3.4、Tween

在默认情况下,AnimationController对象的范围是 0.0-0.1。如果需要不同的范围或者不同的数据类型,可以使用Tween配置动画来插入不同的范围或数据类型。例如下面的示例中,Tween的范围是 -200 到 0.0。

1
final Tween doubleTween = Tween<double>(begin: -200.0, end: 0.0);

Tween是无状态的对象,只有beginendTween的这种单一用途用来定义从输入范围到输出范围的映射。输入范围一般为 0.0-1.0,但这并不是必须的。

Tween源自Animatable<T>,而不是Animation<T>。像动画这样的可动画元素不必重复输出。例如,ColorTween指定了两种颜色之间的过程。

1
2
final Tween colorTween =
    ColorTween(begin: Colors.transparent, end: Colors.black54);

Tween对象不存储任何状态。而是提供evaluate(Animation<double> animation)方法,将映射函数应用于动画当前值。Animation对象的当前值可以在.value方法中找到。evaluate方法还执行一些内部处理内容,比如确保当动画值在 0.0 和1.0 时分别返回起始点和终点。

3.4.1、Tween.animate

要使用Tween对象,请在Tween调用animate(),传入控制器对象。例如,下面的代码在 500 ms 的进程中生成 0-255 范围内的整数值。

1
2
3
AnimationController controller = AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(controller);

注意:animate() 方法会返回一个Animation,而不是Animatable

下面的示例展示了一个控制器,一个曲线,和一个Tween

1
2
3
4
5
AnimationController controller = AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
final Animation<double> curve =
    CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(curve);

四、动画示例

现有一需求,点击scale按钮,可以将一个Icon变大或缩小,并且中途还可以调转动画的方向。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyPage(title: '动画演示'),
    );
  }
}

class MyPage extends StatefulWidget {
  const MyPage({super.key, required this.title});

  final String title;

  @override
  State<MyPage> createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: const MyWidget(),
    );
  }
}

class MyWidget extends StatefulWidget {
  const MyWidget({super.key});

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
      reverseDuration: const Duration(seconds: 1),
    );
    _animation = CurvedAnimation(
      parent: _animationController,
      curve: Curves.easeIn,
      reverseCurve: Curves.bounceIn,
    );
    _animation = Tween(begin: 200.0, end: 300.0).animate(_animation)
      ..addListener(() {
        setState(() {

        });
      });
  }

  void _scale() {
    if (_animation.status == AnimationStatus.completed ||
        _animation.status == AnimationStatus.forward) {
      _animationController.reverse();
    } else if (_animation.status == AnimationStatus.dismissed ||
        _animation.status == AnimationStatus.reverse) {
      _animationController.forward();
    }
  }

  @override
  void dispose() {
    super.dispose();
    _animationController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    debugPrint('build');
    return Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(
            Icons.account_circle,
            size: _animation.value,
          ),
          OutlinedButton(
            onPressed: _scale,
            child: const Text('scale'),
          )
        ],
      ),
    );
  }
}

程序运行起来后,UI效果为:

五、分析动画源码

5.1、_animationController.forward()

从动画的启动方法入手,也就是AnimationControllerforward方法,看下它的源码。

1
2
3
4
5
6
7
8
9
TickerFuture forward({ double? from }) {
  // 指定动画方向为正向
  _direction = _AnimationDirection.forward;
  // 因为没有传入from,所以这里为null,此处不会执行
  if (from != null) {
    value = from;
  }
  return _animateToInternal(upperBound);
}

AnimationControllerforward方法中,执行了 _animateToInternal 方法,这里传入了upperBound,它是该动画被视为完成的值。

upperBound是在AnimationController的构造方法中传入,只是本示例中没有传入,所以取的是默认值为 1.0。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class AnimationController extends Animation<double>
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {

  AnimationController({
    double? value,
    this.duration,
    this.reverseDuration,
    this.debugLabel,
    this.lowerBound = 0.0,
    // 默认值
    this.upperBound = 1.0,
    this.animationBehavior = AnimationBehavior.normal,
    required TickerProvider vsync,
  }) : assert(upperBound >= lowerBound),
       _direction = _AnimationDirection.forward {
    _ticker = vsync.createTicker(_tick);
    _internalSetValue(value ?? lowerBound);
  }
}

继续跟踪AnimationController_animateToInternal 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
TickerFuture _animateToInternal(double target, { Duration? duration, Curve curve = Curves.linear }) {
  double scale = 1.0;
  /// SemanticsBinding.instance.disableAnimations用来判断该平台是否要求禁用或简化动画。
  /// 可通过设置debugSemanticsDisableAnimations可以覆盖此设置以进行测试或调试
  /// 例如:
  ///
  /// ```dart
  /// import 'package:flutter/rendering.dart';
  ///
  /// void main() {
  ///   // debugSemanticsDisableAnimations = true;
  ///   runApp(const MyApp());
  /// }
  /// ```
  /// 本示例中很明显没有禁用动画,所以此处不执行,scale的值仍然为1.0
  if (SemanticsBinding.instance.disableAnimations) {
    switch (animationBehavior) {
      case AnimationBehavior.normal:
        // 由于该框架无法处理零持续时间动画,因此我们以正常持续时间的 5% 运行它,以将大多数动画限制为单帧。
        // 理想情况下,框架将能够处理零持续时间动画,但是,如果不延迟至少一帧,永久重复动画的常见模式可能会导致无限循环。
        // Since the framework cannot handle zero duration animations, we run it at 5% of the normal
        // duration to limit most animations to a single frame.
        // Ideally, the framework would be able to handle zero duration animations, however, the common
        // pattern of an eternally repeating animation might cause an endless loop if it weren't delayed
        // for at least one frame.
        scale = 0.05;
      case AnimationBehavior.preserve:
        break;
    }
  }
  // simulationDuration是模拟持续时间,但是由于duration参数没有传入,所以simulationDuration为null
  Duration? simulationDuration = duration;
  // 因为simulationDuration为null,所以此处执行
  if (simulationDuration == null) {
    assert(!(this.duration == null && _direction == _AnimationDirection.forward));
    assert(!(this.duration == null && _direction == _AnimationDirection.reverse && reverseDuration == null));
    // 获取动画的范围,因为在AnimationController构造方法中,upperBound与lowerBound参数均没传入,所以都是取默认值,upperBound为1.0,lowerBound为0.0,那么此处range为1.0
    final double range = upperBound - lowerBound;
    // range.isFinite是判断range这个数字是否是有限的。
    // 唯一的非有限数是 NaN 值、正无穷大和负无穷大。所有整数都是有限的。
    // 所有数字都满足isInfinite、isFinite和isNaN之一
    // 因为range为1.0,所以range.isFinite是true
    // 因为target为传入的upperBound,所以它的值为1.0,_value默认值为0.0,所以remainingFraction的最终计算结果为1.0,也就是动画的执行进度剩余百分比
    final double remainingFraction = range.isFinite ? (target - _value).abs() / range : 1.0;
    // 如果动画方向为反向并且反向动画时间reverseDuration(此处reverseDuration也就是AnimationController构造方法传入的reverseDuration)不为null,
    // 那么directionDuration为反向动画时间,否则为正向动画时间duration(此处duration也就是AnimationController构造方法传入的duration)
    // 在前面的forward方法中,_direction已经被赋值为_AnimationDirection.forward,所以这里directionDuration为this.duration,也就是1s
    final Duration directionDuration =
      (_direction == _AnimationDirection.reverse && reverseDuration != null)
      ? reverseDuration!
      : this.duration!;
    // 那么最终计算后的simulationDuration模拟持续时间为1s
    simulationDuration = directionDuration * remainingFraction;
  } else if (target == value) {
    // Already at target, don't animate.
    simulationDuration = Duration.zero;
  }
  // 停止运行该动画。这不会触发任何通知。动画停止在当前状态。
  stop();
  // 因为simulationDuration为1s,所以这里不执行
  if (simulationDuration == Duration.zero) {
    if (value != target) {
      _value = clampDouble(target, lowerBound, upperBound);
      notifyListeners();
    }
    _status = (_direction == _AnimationDirection.forward) ?
      AnimationStatus.completed :
      AnimationStatus.dismissed;
    _checkStatusChanged();
    return TickerFuture.complete();
  }
  assert(simulationDuration > Duration.zero);
  assert(!isAnimating);
  // 执行_startSimulation方法,并且创建_InterpolationSimulation实例并作为参数传入该方法
  return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));
}

AnimationController_animateToInternal 方法中,执行了 _startSimulation 方法,并且创建 _InterpolationSimulation 实例并作为参数传入该方法。

继续跟踪AnimationController_startSimulation 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
TickerFuture _startSimulation(Simulation simulation) {
  assert(!isAnimating);
  // 将前面创建的_InterpolationSimulation实例赋值给成员变量_simulation
  _simulation = simulation;
  // 初始化动画上一帧的时间,动画还没开始,所以为0
  _lastElapsedDuration = Duration.zero;
  // 计算_value,也就是外部使用的_animation.value
  // clampDouble主要对simulation.x(0.0)这个结果做了大小限制(lowerBound <= 结果 <= upperBound)
  // 此处执行的是_InterpolationSimulation的x方法,_value的最终计算结果为0.0
  _value = clampDouble(simulation.x(0.0), lowerBound, upperBound);
  // 启动Ticker,每个动画帧均会调用一次回调,这个等下再分析
  final TickerFuture result = _ticker!.start();
  // 因为_direction为_AnimationDirection.forward,所以此时_status状态为AnimationStatus.forward
  _status = (_direction == _AnimationDirection.forward) ?
      AnimationStatus.forward :
      AnimationStatus.reverse;
      // 调用所有状态监听器
  _checkStatusChanged();
  // 最后将TickerFuture返回,但是本示例中没用到,这里就不讲解了
  return result;
}

关于simulation.x(0.0),这里分析下,它执行了 _InterpolationSimulation 的x方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// _InterpolationSimulation的作用就是将Engine层返回的每一帧时间转换为[0.0, 1.0]范围的数值
class _InterpolationSimulation extends Simulation {
    // _begin传入的是_value,它的值为0.0
    // _end传入的是target,它的值为1.0
    // duration传入的是simulationDuration,它的值为1s
    // _curve传入的是curve,它的值为Curves.linear
    // scale传入的是scale,它的值为1.0
  _InterpolationSimulation(this._begin, this._end, Duration duration, this._curve, double scale)
    : assert(duration.inMicroseconds > 0),
    // duration.inMicroseconds表示获取微秒数,所以1s的微秒数为1000000,microsecondsPerSecond表示每秒的微秒数
    // 结合传入的参数值,那么_durationInSeconds最终的计算结果为1s
      _durationInSeconds = (duration.inMicroseconds * scale) / Duration.microsecondsPerSecond;

  final double _durationInSeconds;
  final double _begin;
  final double _end;
  final Curve _curve;

  @override
  double x(double timeInSeconds) {
    // 外部调用simulation.x(0.0)时传入0.0,所以此处timeInSeconds为0.0,所以t最终的计算结果为0
    final double t = clampDouble(timeInSeconds / _durationInSeconds, 0.0, 1.0);
    // 因为t为0,所以执行这里,返回_begin,它的值为0.0
    if (t == 0.0) {
      return _begin;
    } else if (t == 1.0) {
      return _end;
    } else {
      return _begin + (_end - _begin) * _curve.transform(t);
    }
  }
}

关于 _checkStatusChanged 方法,这里分析下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
AnimationStatus _lastReportedStatus = AnimationStatus.dismissed;

void _checkStatusChanged() {
  // 因为在_startSimulation方法中,_status已经被赋值为AnimationStatus.forward,所以这里将成员变量_status赋值给status,那么此时newStatus的值为AnimationStatus.forward
  final AnimationStatus newStatus = status;
  // _lastReportedStatus的默认值为AnimationStatus.dismissed,所以if判断不相等,此处执行
  if (_lastReportedStatus != newStatus) {
    _lastReportedStatus = newStatus;
    // 将newStatus通知给所有已注册的状态监听器
    notifyStatusListeners(newStatus);
  }
}

因为AnimationController混入了AnimationLocalStatusListenersMixin,所以这里的notifyStatusListeners方法执行的是AnimationLocalStatusListenersMixinnotifyStatusListeners方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void notifyStatusListeners(AnimationStatus status) {
  final List<AnimationStatusListener> localListeners = _statusListeners.toList(growable: false);
  for (final AnimationStatusListener listener in localListeners) {
    try {
      // 遍历所有AnimationStatusListener,将status回调出去
      if (_statusListeners.contains(listener)) {
        listener(status);
      }
    } catch (exception, stack) {
      InformationCollector? collector;
      assert(() {
        collector = () => <DiagnosticsNode>[
          DiagnosticsProperty<AnimationLocalStatusListenersMixin>(
            'The $runtimeType notifying status listeners was',
            this,
            style: DiagnosticsTreeStyle.errorProperty,
          ),
        ];
        return true;
      }());
      FlutterError.reportError(FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'animation library',
        context: ErrorDescription('while notifying status listeners for $runtimeType'),
        informationCollector: collector,
      ));
    }
  }
}

先回到 _startSimulation 方法上来,其实整个 _startSimulation 方法的重点是_ticker!.start(),因为它启动了动画,那么 _ticker 是什么?又在哪创建的?

5.2、AnimationController的构造方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class AnimationController extends Animation<double>
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {

  AnimationController({
    double? value,
    this.duration,
    this.reverseDuration,
    this.debugLabel,
    this.lowerBound = 0.0,
    this.upperBound = 1.0,
    this.animationBehavior = AnimationBehavior.normal,
    required TickerProvider vsync,
  }) : assert(upperBound >= lowerBound),
       _direction = _AnimationDirection.forward {
        // 此处执行了TickerProvider的createTicker方法
    _ticker = vsync.createTicker(_tick);
    _internalSetValue(value ?? lowerBound);
  }
}

AnimationController的构造方法中,执行了vsync.createTicker(_tick)这行代码,也就是执行了TickerProvidercreateTicker方法,而createTicker方法需要传入的参数是一个TickerCallback,这里传入的实参为AnimationController_tick 方法。

在本示例中,vsync参数传入的是this,即混入的SingleTickerProviderStateMixin,它是专门用来提供和管理单个Ticker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class _MyWidgetState extends State<MyWidget> with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
        // 传入this,即混入的SingleTickerProviderStateMixin
      vsync: this,
      duration: const Duration(seconds: 1),
      reverseDuration: const Duration(seconds: 1),
    );
  }
}

因此,vsync.createTicker实际上执行了SingleTickerProviderStateMixincreateTicker方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
 Ticker? _ticker;

 @override
 Ticker createTicker(TickerCallback onTick) {
   assert(() {
     if (_ticker == null) {
       return true;
     }
     throw FlutterError.fromParts(<DiagnosticsNode>[
       ErrorSummary('$runtimeType is a SingleTickerProviderStateMixin but multiple tickers were created.'),
       ErrorDescription('A SingleTickerProviderStateMixin can only be used as a TickerProvider once.'),
       ErrorHint(
         'If a State is used for multiple AnimationController objects, or if it is passed to other '
         'objects and those objects might use it more than one time in total, then instead of '
         'mixing in a SingleTickerProviderStateMixin, use a regular TickerProviderStateMixin.',
       ),
     ]);
   }());
   // 这里创建了Ticker实例并赋值给成员变量_ticker
   // 此处Ticker构造方法传入的参数onTick,它的实例是AnimationController的_tick方法。
   _ticker = Ticker(onTick, debugLabel: kDebugMode ? 'created by ${describeIdentity(this)}' : null);
   _updateTickerModeNotifier();
   _updateTicker(); // Sets _ticker.mute correctly.
   // 把Ticker实例返回
   return _ticker!;
 }
}

_startSimulation 方法执行了 _ticker!.start() 时,就会执行上面创建的 _tickerstart方法,跟踪下Tickerstart方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
TickerFuture start() {
 assert(() {
   if (isActive) {
     throw FlutterError.fromParts(<DiagnosticsNode>[
       ErrorSummary('A ticker was started twice.'),
       ErrorDescription('A ticker that is already active cannot be started again without first stopping it.'),
       describeForError('The affected ticker was'),
     ]);
   }
   return true;
 }());
 assert(_startTime == null);
 // 此处创建了一个TickerFuture对象,表示正在进行的Ticker序列的对象。
 // Ticker.start方法返回一个TickerFuture。如果使用Ticker.stop停止Ticker ,并将canceled参数设置为 false(默认值),则TickerFuture将成功完成。
 // 如果Ticker在未停止的情况下被处置,或者如果在canceled设置为 true 的情况下停止,则此 Future 将永远不会完成
 _future = TickerFuture._();
 // shouldScheduleTick表示是否应该安排一个tick
 // 不应安排tick的原因包括:
 // 1、已经为下一帧安排了一个tick
 // 2、ticker未激活(尚未调用start)
 // 3、ticker没有在ticking,例如因为它被muted (请参阅isTicking )
 // 通过断点调试来看,此处shouldScheduleTick为true,所以执行了scheduleTick方法。
 if (shouldScheduleTick) {
   scheduleTick();
 }
 // schedulerPhase表示调度程序当前运行的阶段,通过断点调试来看,SchedulerBinding.instance.schedulerPhase.index的值为0,
 // 也就是schedulerPhase所处状态为idle,所以SchedulerBinding.instance.schedulerPhase.index > SchedulerPhase.idle.index不满足,不会执行这里代码
 if (SchedulerBinding.instance.schedulerPhase.index > SchedulerPhase.idle.index &&
     SchedulerBinding.instance.schedulerPhase.index < SchedulerPhase.postFrameCallbacks.index) {
   _startTime = SchedulerBinding.instance.currentFrameTimeStamp;
 }
 // 返回一个TickerFuture对象,此处的返回就是forward方法的返回值
 return _future!;
}

Tickerstart方法中,执行了scheduleTick方法,跟踪看看。

1
2
3
4
5
6
7
// scheduleTick方法的作用是为下一帧安排一个tick
void scheduleTick({ bool rescheduling = false }) {
  assert(!scheduled);
  assert(shouldScheduleTick);
  // 执行了SchedulerBinding的scheduleFrameCallback方法,该方法需要传入一个FrameCallback实例,该实例就是Ticker的_tick方法。
  _animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
}

先看下SchedulerBindingscheduleFrameCallback方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
  // 执行了scheduleFrame方法。调用dart:ui.PlatformDispatcher.scheduleFrame来安排新帧
  // 调用此函数后,引擎将(最终)调用handleBeginFrame 
  scheduleFrame();
  // 下一帧回调实例的Id,此处+1
  _nextFrameCallbackId += 1;
  // 将callback包装成_FrameCallbackEntry对象,存入瞬态回调Map集合_transientCallbacks中
  _transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
  return _nextFrameCallbackId;
}

看下SchedulerBindingscheduleFrame方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void scheduleFrame() {
  if (_hasScheduledFrame || !framesEnabled) {
    return;
  }
  assert(() {
    if (debugPrintScheduleFrameStacks) {
      debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
    }
    return true;
  }());
  // 执行ensureFrameCallbacksRegistered方法,确保PlatformDispatcher.onBeginFrame和PlatformDispatcher.onDrawFrame的回调已注册
  ensureFrameCallbacksRegistered();
  // 执行platformDispatcher的scheduleFrame,请求在下一个适当的机会调用onBeginFrame和onDrawFrame回调
  platformDispatcher.scheduleFrame();
  _hasScheduledFrame = true;
}

看下ensureFrameCallbacksRegistered方法。

1
2
3
4
5
6
7
@protected
void ensureFrameCallbacksRegistered() {
  // 为platformDispatcher的onBeginFrame注册回调,这里具体实现是_handleBeginFrame方法
  platformDispatcher.onBeginFrame ??= _handleBeginFrame;
  // 为platformDispatcher的onDrawFrame注册回调,这里具体实现是_handleDrawFrame方法
  platformDispatcher.onDrawFrame ??= _handleDrawFrame;
}

再看下platformDispatcher.scheduleFrame(),这里是external修饰,说明调用了Engine层的方法了,它在不同平台有不同实现。

1
2
3
4
5
// 请求在下一个适当的机会调用onBeginFrame和onDrawFrame回调。
void scheduleFrame() => _scheduleFrame();

@Native<Void Function()>(symbol: 'PlatformConfigurationNativeApi::ScheduleFrame')
external static void _scheduleFrame();

这里就不深入底层实现了,不过可以猜测,在Android中应该是调用了Choreographer注册了Vysnc信号监听,然后再回调给Flutter,具体留给你们去验证了。

等到Engine层处理完成,将会回调给Framework层的 _beginFrame 方法,在 _beginFrame 方法中执行了PlatformDispatcher_beginFrame 方法。

1
2
3
4
5
@pragma('vm:entry-point')
void _beginFrame(int microseconds, int frameNumber) {
  PlatformDispatcher.instance._beginFrame(microseconds);
  PlatformDispatcher.instance._updateFrameData(frameNumber);
}

PlatformDispatcher_beginFrame 方法中,执行了 _invoke1 方法,第一个参数传入的是onBeginFrame回调,这个就是之前讲过的SchedulerBindingensureFrameCallbacksRegistered方法中注册的回调。

1
2
3
4
5
6
7
8
void _beginFrame(int microseconds) {
  _invoke1<Duration>(
    // 此处传入onBeginFrame
    onBeginFrame,
    _onBeginFrameZone,
    Duration(microseconds: microseconds),
  );
}

来看下 _invoke1 方法,执行了callback(),也就是调用了onBeginFrame的回调引用,那么就会触发SchedulerBinding_handleBeginFrame 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void _invoke1<A>(void Function(A a)? callback, Zone zone, A arg) {
  if (callback == null) {
    return;
  }
  if (identical(zone, Zone.current)) {
    // 此处调用外部方法引用,传入参数是arg,也就是一个Duration,这个时间是每一帧的时间
    callback(arg);
  } else {
    zone.runUnaryGuarded<A>(callback, arg);
  }
}

继续看SchedulerBinding_handleBeginFrame 方法,执行了handleBeginFrame方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
void handleBeginFrame(Duration? rawTimeStamp) {
  _frameTimelineTask?.start('Frame');
  _firstRawTimeStampInEpoch ??= rawTimeStamp;
  _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
  if (rawTimeStamp != null) {
    _lastRawTimeStamp = rawTimeStamp;
  }

  assert(() {
    _debugFrameNumber += 1;

    if (debugPrintBeginFrameBanner || debugPrintEndFrameBanner) {
      final StringBuffer frameTimeStampDescription = StringBuffer();
      if (rawTimeStamp != null) {
        _debugDescribeTimeStamp(_currentFrameTimeStamp!, frameTimeStampDescription);
      } else {
        frameTimeStampDescription.write('(warm-up frame)');
      }
      _debugBanner = '▄▄▄▄▄▄▄▄ Frame ${_debugFrameNumber.toString().padRight(7)}   ${frameTimeStampDescription.toString().padLeft(18)} ▄▄▄▄▄▄▄▄';
      if (debugPrintBeginFrameBanner) {
        debugPrint(_debugBanner);
      }
    }
    return true;
  }());

  assert(schedulerPhase == SchedulerPhase.idle);
  _hasScheduledFrame = false;
  try {
    // TRANSIENT FRAME CALLBACKS
    _frameTimelineTask?.start('Animate');
    _schedulerPhase = SchedulerPhase.transientCallbacks;
    final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
    _transientCallbacks = <int, _FrameCallbackEntry>{};
    // 此处遍历执行_transientCallbacks,也就是传入的Ticker的_tick方法实例
    callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
      if (!_removedIds.contains(id)) {
        _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp!, callbackEntry.debugStack);
      }
    });
    _removedIds.clear();
  } finally {
    _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
  }
}

看下Ticker_tick 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void _tick(Duration timeStamp) {
  assert(isTicking);
  assert(scheduled);
  _animationId = null;

  _startTime ??= timeStamp;
  // 执行了SingleTickerProviderStateMixin的createTicker方法中传入的onTick参数
  // 也就是执行了AnimationController构造方法中的vsync.createTicker(_tick)传入的_tick方法实例
  _onTick(timeStamp - _startTime!);

  // The onTick callback may have scheduled another tick already, for
  // example by calling stop then start again.
  if (shouldScheduleTick) {
    scheduleTick(rescheduling: true);
  }
}

看下AnimationController_tick 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// elapsed是Engine层返回的每一帧时间Duration,每一帧的时间都不一样
void _tick(Duration elapsed) {
  _lastElapsedDuration = elapsed;
  // 将拿到的每一帧的时间单位改为秒
  final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
  assert(elapsedInSeconds >= 0.0);
  // 执行clampDouble方法获取_value,此时的_value依然是_simulation控制,也就是_InterpolationSimulation实例
  _value = clampDouble(_simulation!.x(elapsedInSeconds), lowerBound, upperBound);
  // 等到elapsedInSeconds时间达到我们设定的动画时间Duration,也就是本示例中的1s,表示动画执行完成
  if (_simulation!.isDone(elapsedInSeconds)) {
      // 如果_direction是_AnimationDirection.forward,更改_status为AnimationStatus.completed,否则改为AnimationStatus.dismissed
    _status = (_direction == _AnimationDirection.forward) ?
      AnimationStatus.completed :
      AnimationStatus.dismissed;
      // 停止当前动画,移除当前瞬态帧回调,那么_tick方法就不会被再回调执行
    stop(canceled: false);
  }
  // 执行notifyListeners方法,通知所有listeners回调
  notifyListeners();
  // 执行_checkStatusChanged,通知所有status listeners回调
  _checkStatusChanged();
}

至此,forward方法完成了第一帧动画的初始操作,可以说它启动了动画。等到在addListener方法中执行setState方法时,就会触发下一轮 _tick 方法的执行以及build方法的执行,这样一帧一帧执行直到动画完成。

关于setState方法的分析参考解读Flutter源码之setState一文。

5.3、_animation.value

通过 _animation.value 最初拿到的是AnimationController_value,也就是上面 _tick 方法中计算出来的 _value,为什么这么说呢?看下面分析就知道了。

在本示例中,CurvedAnimation的构造方法将 _animationController 作为参数传了进去。

1
2
3
4
5
6
_animation = CurvedAnimation(
  // 传入_animationController
    parent: _animationController,
    curve: Curves.easeIn,
    reverseCurve: Curves.bounceIn,
);

可以发现CurvedAnimation混入了AnimationWithParentMixin

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> {

  CurvedAnimation({
    required this.parent,
    required this.curve,
    this.reverseCurve,
  }) {
    // 根据_animationController的初始状态,更新Curved曲线的方向
    _updateCurveDirection(parent.status);
    // 给_animationController注册状态监听,用来更新后续Curved曲线的方向
    parent.addStatusListener(_updateCurveDirection);
  }

  @override
  final Animation<double> parent;
}

看下AnimationWithParentMixin,可以发现此时的listeners管理以及status依然是转发给parent处理,也就是还是由AnimationController来管理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
mixin AnimationWithParentMixin<T> {

  Animation<T> get parent;

  void addListener(VoidCallback listener) => parent.addListener(listener);

  void removeListener(VoidCallback listener) => parent.removeListener(listener);

  void addStatusListener(AnimationStatusListener listener) => parent.addStatusListener(listener);

  void removeStatusListener(AnimationStatusListener listener) => parent.removeStatusListener(listener);

  AnimationStatus get status => parent.status;
}

但是,CurvedAnimation重写了AnimationController成员变量value,可以看到在value中拿到parent.value后,执行了 activeCurve.transform(t) 进行转换。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@override
double get value {
  // 获取Curves曲线的方向
  final Curve? activeCurve = _useForwardCurve ? curve : reverseCurve;

  // 获取AnimationController的value值,这个值之前由simulation计算得来
  final double t = parent.value;
  if (activeCurve == null) {
    return t;
  }
  // 控制动画范围
  if (t == 0.0 || t == 1.0) {
    assert(() {
      final double transformedValue = activeCurve.transform(t);
      final double roundedTransformedValue = transformedValue.round().toDouble();
      if (roundedTransformedValue != t) {
        throw FlutterError(
          'Invalid curve endpoint at $t.\n'
          'Curves must map 0.0 to near zero and 1.0 to near one but '
          '${activeCurve.runtimeType} mapped $t to $transformedValue, which '
          'is near $roundedTransformedValue.',
        );
      }
      return true;
    }());
    return t;
  }
  // 执行Curve转换
  return activeCurve.transform(t);
}

这里执行了Curve的父类ParametricCurvetransform方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
abstract class Curve extends ParametricCurve<double> {

  const Curve();

  @override
  double transform(double t) {
    // 控制动画范围
    if (t == 0.0 || t == 1.0) {
      return t;
    }
    // 执行父类ParametricCurve的transform方法
    return super.transform(t);
  }
}

ParametricCurvetransform方法中,执行了transformInternal方法,该方法由子类实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
abstract class ParametricCurve<T> {

  const ParametricCurve();

  T transform(double t) {
    assert(t >= 0.0 && t <= 1.0, 'parametric value $t is outside of [0, 1] range.');
    // 执行transformInternal方法
    return transformInternal(t);
  }

  @protected
  T transformInternal(double t) {
    throw UnimplementedError();
  }
}

在本示例中,对于动画的方向是正向的,CurvedAnimation构造方法中传入的子类是Curves.easeIn;对于动画的方向是反向的,CurvedAnimation构造方法中传入的子类是Curves.bounceIn。因为本示例只讲解forward方法,所以这里只以Curves.easeIn为例讲解,看下源码。

1
static const Cubic easeIn = Cubic(0.42, 0.0, 1.0, 1.0);

看下Cubic实现的transformInternal方法。它是返回点t处的曲线值。但是给定的参数值t将介于 0.0 和 1.0 之间(包含 0.0 和 1.0),那和我们实际Widget的参数映射还是不一致,比如本示例中Iconsize参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@override
double transformInternal(double t) {
  double start = 0.0;
  double end = 1.0;
  while (true) {
    final double midpoint = (start + end) / 2;
    final double estimate = _evaluateCubic(a, c, midpoint);
    if ((t - estimate).abs() < _cubicErrorBound) {
      return _evaluateCubic(b, d, midpoint);
    }
    if (estimate < t) {
      start = midpoint;
    } else {
      end = midpoint;
    }
  }
}

想要和我们实际Widget的参数映射一致,就需要讲到Tween了,看下本示例中的实现。

1
_animation = Tween(begin: 200.0, end: 300.0).animate(_animation)

看下Tweenanimate方法,方法参数传入了上一步创建的CurvedAnimation,返回的是 _AnimatedEvaluation 实例。

1
2
3
Animation<T> animate(Animation<double> parent) {
  return _AnimatedEvaluation<T>(parent, this);
}

看下 _AnimatedEvaluation实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {
  _AnimatedEvaluation(this.parent, this._evaluatable);

  @override
  final Animation<double> parent;

  final Animatable<T> _evaluatable;

  @override
  T get value => _evaluatable.evaluate(parent);
}

可以看到 _AnimatedEvaluation 继承自Animation,和CurvedAnimation一样混入了AnimationWithParentMixin,此时的listeners管理以及status是转发给parent处理,也就是CurvedAnimation

而且 _AnimatedEvaluation 重写了AnimationController成员变量value,当执行 _animation.value时就会调用该value。可以看到在value中执行了 _evaluatable.evaluate(parent) 进行转换。

_evaluatable 就是 _AnimatedEvaluation 构造方法传入进来的this,指的是Tween实例,又因为Tween继承自Animatable,看下Animatableevaluate方法。

1
2
3
4
5
6
abstract class Animatable<T> {

  T transform(double t);

  T evaluate(Animation<double> animation) => transform(animation.value);    
}

可以看到啊,在Animatableevaluate方法中,执行了transform方法,传入的是animation.value,实际上就拿到了CurvedAnimationvalue,这个方法由子类实现,现在看下Tweentransform方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@override
T transform(double t) {
  if (t == 0.0) {
    return begin as T;
  }
  if (t == 1.0) {
    return end as T;
  }
  return lerp(t);
}

Tweentransform方法中,执行了lerp方法进行Widget参数映射到[0.0, 1.0]范围。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@protected
T lerp(double t) {
  assert(begin != null);
  assert(end != null);
  assert(() {
    // Assertions that attempt to catch common cases of tweening types
    // that do not conform to the Tween requirements.
    dynamic result;
    try {
      // ignore: avoid_dynamic_calls
      result = (begin as dynamic) + ((end as dynamic) - (begin as dynamic)) * t;
      result as T;
      return true;
    } on NoSuchMethodError {
      throw FlutterError.fromParts(<DiagnosticsNode>[
        ErrorSummary('Cannot lerp between "$begin" and "$end".'),
        ErrorDescription(
          'The type ${begin.runtimeType} might not fully implement `+`, `-`, and/or `*`. '
          'See "Types with special considerations" at https://api.flutter.dev/flutter/animation/Tween-class.html '
          'for more information.',
        ),
        if (begin is Color || end is Color)
          ErrorHint('To lerp colors, consider ColorTween instead.')
        else if (begin is Rect || end is Rect)
          ErrorHint('To lerp rects, consider RectTween instead.')
        else
          ErrorHint(
            'There may be a dedicated "${begin.runtimeType}Tween" for this type, '
            'or you may need to create one.',
          ),
      ]);
    } on TypeError {
      throw FlutterError.fromParts(<DiagnosticsNode>[
        ErrorSummary('Cannot lerp between "$begin" and "$end".'),
        ErrorDescription(
          'The type ${begin.runtimeType} returned a ${result.runtimeType} after '
          'multiplication with a double value. '
          'See "Types with special considerations" at https://api.flutter.dev/flutter/animation/Tween-class.html '
          'for more information.',
        ),
        if (begin is int || end is int)
          ErrorHint('To lerp int values, consider IntTween or StepTween instead.')
        else
          ErrorHint(
            'There may be a dedicated "${begin.runtimeType}Tween" for this type, '
            'or you may need to create one.',
          ),
      ]);
    }
  }());
  // ignore: avoid_dynamic_calls
  return (begin as dynamic) + ((end as dynamic) - (begin as dynamic)) * t as T;
}

关于lerp方法,这里插一嘴,因为Tween的子类很多,所以lerp方法可以由Tween的子类去实现,例如Tween的子类SizeTween,看下SizeTweenlerp方法。

1
2
3
4
5
6
7
class SizeTween extends Tween<Size?> {

  SizeTween({ super.begin, super.end });

  @override
  Size? lerp(double t) => Size.lerp(begin, end, t);
}

可以发现SizeTweenlerp方法,它是调用了Sizelerp方法。除了Size之外,还有很多类也重写了lerp方法,比如ColorBorderRadiusBorder等等,它们分别对应的是ColorTweenBorderRadiusTweenBorderTween

总结一下本示例中 _animation.value 的执行过程:

当外部调用_animation.value时,就会执行Tween中的value,在Tween的value中就会执行CurvedAnimation中的value,在CurvedAnimation的value中就会执行AnimationController的value。

所以说 _animation.value 的执行是一个向上转发,向下执行的过程。

六、动画总结

当执行forward方法时,就会启动动画,此时只是执行了动画的第一帧,然后就会触发addListener的回调,在addListener的回调中,执行了setState方法。

接着又会触发addListener的回调与build方法的执行,这样就形成了递归执行的过程,在这个过程中,通过引用 _animation.value 可以拿到每一帧的值,这个值是经过了simulation模拟计算将每一帧时间转换为[0.0,1.0]范围,在[0.0,1.0]范围内也经过了Curve曲线速度的转换,最后再通过Tween映射为具体的Widget的参数范围。

每次build方法执行时,_animation.value 的值会重新计算,从而改变Widget参数完成整个动画过程。