Flutter动画分析之AnimationController

本文讨论的 Flutter 动画主要限定在: 随着每一帧的变化,修改 Flutter Widget 的大小、颜色、位置等属性,使之看起来从一种状态渐变为另外一种状态 这一范围。

Flutter 中关于动画的类有很多,为了便于分析,将其分为两大类:

  • Flutter 框架底层实现动画的各个类,比如 AnimationController、Ticker、Tween、Curve 等
  • 基于底层实现,提供进一步封装的 Flutter 动画相关的 Widget 类,比如 AnimatedWidget、ImplicitlyAnimatedWidget 和他们的子类。

他们的关系如下:

AnimationController 通过 Ticker 监听 Flutter 屏幕帧刷新:

Flutter中AnimationController与Ticker关系

每一帧刷新后,AnimationController 监听并根据 Duration 等计算出当前的 Animation.value;
此外也可以通过 Tween 将 double 类型转化为其他的类型比如 Offset 等;
上述两种方式中 value 都是随着时间线性变化,而 Curve 可以与 CurveTween、AnimationController 等结合使 value 实现非线性的变化。

Flutter各种动画底层类关系

当随着时间变化,计算出当前的 Animation.value 时,便可以根据此值修改 Flutter Widget 的各个属性,从而实现动画的视觉效果。

Flutter中与动画有关的Widget

从上图可以看到,Flutter 提供的动画 Widget 主要分为两大类:

  • ImplicitlyAnimatedWidget 隐式动画,关于动画的开始、停止等都封装在 Widget 内部,只要 Widget 前后传入的值不同便可以自动从 old 渐变到 new,内置的这些类主要以 AnimatedFoo 命名。

  • AnimatedWidget,显式动画,需要使用者自己创建 Animation(一般是 AnimationController)并通过其管理动画,此类 Widget 主要是监听 AnimationController 的值并刷新 Widget 的内容。

    此类 Widget 主要有三种使用方式:

    • 继承 AnimatedWidget
    • 使用 AnimatedBuilder
    • 使用各种内置的 AnimatedWidget 子类,一般以 FooTransition 命名。

对于 Flutter 中这些与动画有关的类如何选择,Flutter 官方给了一张图:

如何实现Flutter中的动画

简单来说,Flutter 有一些内置的动画,在要写动画的时候,可以依次考虑(实现程度由易到难):

  • AnimatedFoo 参考文章,设置新的状态,这些控件会自动从之前的状态切换到新状态
  • TweenAnimationBuilder 参考文章,将任意属性在 Tween 指定的范围变化,和上面的 AnimatedFoo 都是属于Implicitly animated widgets(隐式动画,由系统控件控制动画)。
  • FooTranslation
  • AnimatedBuilder / AnimatedWidget
  • CustomPainter

本文主要分析 AnimationController 及其相关类。

源码分析

AnimationController 是 Flutter 中动画的基石,它继承自 Animation,根据不同的方法调用创建对应的 Simulation 并开始监听传入的 Ticker;

每当 Flutter 中帧刷新时,从_simulation 中获取当前 Animation._value 并对 listener 发出通知;

这样需要使用 Animation.value 的各个 Widget 便可以根据其值修改自身属性,实现动画视觉效果。

Animation

根据上述分析,我们首先来看一下 Animation 类:

An animation with a value of type T

Animation 主要的作用是持有 value 和 status,并允许其他对象监听二者的变化。

1
2
3
4
5
6
abstract class Animation<T> extends Listenable implements ValueListenable<T> {
/// The current value of the animation.
T get value;
/// The current status of this animation.
AnimationStatus get status;
}

Animation 继承自 Listenable,实现 ValueListenable 接口,其他类可以通过 addListener/removeListener 或者 addStatusListener/removeStatusListener 监听 Animation 的 value 或者 status 变化。

Animation 共有 4 种状态:dismissed、forward、reverse、completed。

除此之外,Animation.drive 方法可以创建一个新的将传入的 Animatable 应用到自身的 Animation。

1
2
3
4
5
6
@optionalTypeArgs
Animation<U> drive<U>(Animatable<U> child) {
assert(this is Animation<double>);
// 通过Animatable.transform将此Animation.value的值从double转化为U
return child.animate(this as Animation<double>);
}

也就是说,提供了将 Animation<double> 转化为 Animation<U> 类型的方法。

其他子类

除了后面要详细分析的 AnimationController 之外,Animation 还有如下子类:

class 说明
AlwaysStoppedAnimation 永远停留在指定值的 animation
ProxyAnimation 代理 Animation,适用于动画可能会变化的情况,先使用 ProxyAnimation 应用一个 Animation,然后再修改为其他 Animation(不用手动添加移除 listener)
ReverseAnimation 返回和当前 animation 反方向的 Animation
CurvedAnimation 可以为传入的 animation 使用 Curve 的 animation,适用于将原先线性变化的 Animation 改为非线性的
TrainHoppingAnimation 监听传入的两个 Animation<double>,当第二个 Animation 的值超过第一个 Animation 的值时自动切换到第二个并回调 onSwitchedTrain。如果一开始两个 Animation 就在同一个值,则切换到第二个并不会调用 onSwitchedTrain。
CompoundAnimation 可以组合多个 Animation<T>的接口,当 Animation<T> next 处于运动状态时返回 next 的状态,否则返回 Animation<T> first 的状态。

对于上述的 CompoundAnimation,子类只需重写 double get value 方法即可,其有三个子类:

  • AnimationMean 返回 first 和 next 值和的二分之一,值为 double
  • AnimationMax<T extends num> 返回 first 和 next 中最大值
  • AnimationMin<T extends num> 返回 first 和 next 中最小值

AnimationController

A controller for an animation.

尽管有各种子类,但 Animation 最常用的子类是 AnimationController,使用者可以用它来控制、监听动画、创建其他动画。

1
2
class AnimationController extends Animation<double> with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
}

构造方法

AnimationController 有两种构造方法,这两种构造方法主要会初始化以下变量:

  • double value 当前值
  • Duration? duration,reverseDuration 动画正向、反向运行的时长,初始化时可以为 null,但在实际开始动画之前,至少保证 duration 不为 null
  • double lowerBound,double upperBound 当 value 触达此值时,animation 分别被认为是 dismissed、completed
  • Ticker? _ticker 由构造方法中必传的 TickerProvider vsync 创建

他的两个构造方法分别是:

  • AnimationController()

    默认构造方法,double lowerBound,double upperBound 默认分别为 0.0,1.0

  • AnimationController.unbounded()

    不限制 value 值的构造方法,double lowerBound,double upperBound 默认分别为 double.negativeInfinity,double.infinity。适用于没有预设编辑的物理模拟动画。

在这两个构造方法内部,都会通过_ticker = vsync.createTicker(_tick)创建_ticker,并保证当_ticker 回调时执行AnimationController._tick()方法。

这里的 TickerProvider 主要有 2 种:

  • SingleTickerProviderStateMixin 适用于 State 中只有一个 AnimationController 的情况,性能更好
  • TickerProviderStateMixin 适用于 State 生命周期内有多个 AnimationController 的情况

除了从 Animation 继承的方法外,AnimationController 还提供了如下方法,用于操纵动画:

操纵从double? from正向/反向开始动画

  • TickerFuture forward({ double? from })
  • TickerFuture reverse({ double? from })

操纵正向/反向开始朝向double target开始动画,此类动画还可以改变DurationCurve

  • TickerFuture animateTo(double target, { Duration? duration, Curve curve = Curves.linear })
  • TickerFuture animateBack(double target, { Duration? duration, Curve curve = Curves.linear })

上述四种方法,内部都是通过 AnimationController._animateToInternal()方法实现,而此方法内部又是执行 AnimationController._startSimulation(),除此之外,还有以下几类方法内部也是基于_startSimulation()方法实现,主要区别在于不同方法方法创建了不同的 Simulation:

  • TickerFuture repeat({ double? min, double? max, bool reverse = false, Duration? period })
  • TickerFuture fling({ double velocity = 1.0, SpringDescription? springDescription, AnimationBehavior? animationBehavior })
  • TickerFuture animateWith(Simulation simulation)

_startSimulation

AnimationController._startSimulation()方法是其实现动画的基石,其内部主要是开启了_ticker 并发出通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
TickerFuture _startSimulation(Simulation simulation) {
assert(simulation != null);
assert(!isAnimating);
_simulation = simulation;
_lastElapsedDuration = Duration.zero;
_value = simulation.x(0.0).clamp(lowerBound, upperBound);
// 开始ticker
final TickerFuture result = _ticker!.start();
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.forward :
AnimationStatus.reverse;
_checkStatusChanged();
return result;
}

_ticker.start()方法最终通过SchedulerBinding.instance.scheduleFrameCallback()方法监听 Flutter Framework 的帧刷新,并回调 AnimationController._tick 方法

_tick

在此方法内部根据当前时间和_simulation 获取_value 并发出通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void _tick(Duration elapsed) {
_lastElapsedDuration = elapsed;
final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
assert(elapsedInSeconds >= 0.0);
// 通过_simulation获取当前动画的_value
_value = _simulation!.x(elapsedInSeconds).clamp(lowerBound, upperBound);
// 如果动画已经结束了,就停止监听
if (_simulation!.isDone(elapsedInSeconds)) {
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.completed :
AnimationStatus.dismissed;
stop(canceled: false);
}
notifyListeners();
_checkStatusChanged();
}

其他方法

  • void resync(TickerProvider vsync) 使用 vsync 重新创建新的_ticker
  • void stop({ bool canceled = true }) 停止动画,不会触发通知,默认标记动画为 canceled
  • void dispose() 释放资源,动画被标记为 canceled

Simulation

从上面的分析中,我们看到 Simulation 在 AnimationController 动画中也起到很重要的作用:Simulation 主要是在一维空间对物理进行位置、速度等建模。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
abstract class Simulation {
/// Initializes the [tolerance] field for subclasses.
Simulation({ this.tolerance = Tolerance.defaultTolerance });

// 指定时间的位置
double x(double time);

// 指定时间的速度
double dx(double time);

/// Whether the simulation is "done" at the given time.
bool isDone(double time);

// 公差,如果两个数值相差小于等于此值则认为二者相等,用于isDone中
Tolerance tolerance;

@override
String toString() => objectRuntimeType(this, 'Simulation');
}

在 AnimationController 中常用的子类有以下两种:

_InterpolationSimulation

1
_InterpolationSimulation(this._begin, this._end, Duration duration, this._curve, double scale){...}

其 x()方法中除了 t 为 0.0 或 1.0 的情况外,其余时候依靠 Curve(默认为 Curves.linear)计算值。

1
2
3
4
5
6
7
8
9
double x(double timeInSeconds) {
final double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0);
if (t == 0.0)
return _begin;
else if (t == 1.0)
return _end;
else
return _begin + (_end - _begin) * _curve.transform(t);
}

_RepeatingSimulation

1
_RepeatingSimulation(double initialValue, this.min, this.max, this.reverse, Duration period, this.directionSetter){}

没有 Curve,其 double x(double timeInSeconds)方法可以自动判断是否需要反向并修改方向(会触发 status 改变通知):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
double x(double timeInSeconds) {
assert(timeInSeconds >= 0.0);

final double totalTimeInSeconds = timeInSeconds + _initialT;
final double t = (totalTimeInSeconds / _periodInSeconds) % 1.0;
final bool isPlayingReverse = (totalTimeInSeconds ~/ _periodInSeconds).isOdd;

if (reverse && isPlayingReverse) {
directionSetter(_AnimationDirection.reverse);
return ui.lerpDouble(max, min, t)!;
} else {
directionSetter(_AnimationDirection.forward);
return ui.lerpDouble(min, max, t)!;
}
}

此外比较特殊的是他的 isDone 方法一致返回 false,表示不会主动结束动画。

SpringSimulation

用于 fling 方法,创建弹性的模拟

总结

经过上述分析,应该能了解 Flutter 动画中 AnimationController 的作用:

  • AnimationController 通过传入的 TickerProvider 创建并监听 Ticker,确保 Ticker 收到系统帧回调时触发 AnimationController._tick 方法;

  • 提供 forward,reverse,animateTo,animateBack,repeat,fling,animateWith 等方法创建不同的 Simulation 并开启 Ticker,从而可以通过 SchedulerBinding.instance.scheduleFrameCallback 监听 Flutter 每一帧刷新。

    并且在 animateTo,animateBack 方法中可以使用 Curve 实现非线性变化。

  • 当 Flutter 帧刷新时,_tick 方法中通过_simulation 结合时间,lowerBound 和 upperBound 等获取当前值_value 和状态_status 并发出通知。

  • 使用者可以通过 AnimationController 继承自父类 Animation 的 addListener/removeListener、addStatusListener/removeStatusListener 监听动画的值和状态

  • 使用者可以从父类 Animation<double>继承的Animation<U> drive<U>(Animatable<U> child)方法使用 Animatable<U>Animation<double>的 animation 创建一个新的Animation<U>,从而可以得到可以随时间变化过渡的 Offset、Size 等动画。

  • stop 方法可以停止动画

参考资料

动画效果介绍 flutter.cn

Animation api.flutter.dev

AnimationController api.flutter.dev

TickerProvider api.flutter.dev

SingleTickerProviderStateMixin api.flutter.dev