Flutter 动画分析之 AnimationController
本文讨论的 Flutter 动画主要限定在:随着每一帧的变化,修改 Flutter Widget 的大小、颜色、位置等属性,使之看起来从一种状态渐变为另外一种状态 这一范围。
Flutter 中关于动画的类有很多,为了便于分析,将其分为两大类:
- Flutter 框架底层实现动画的各个类,比如 AnimationController、Ticker、Tween、Curve 等
- 基于底层实现,提供进一步封装的 Flutter 动画相关的 Widget 类,比如 AnimatedWidget、ImplicitlyAnimatedWidget 和他们的子类。
他们的关系如下:
AnimationController 通过 Ticker 监听 Flutter 屏幕帧刷新:
每一帧刷新后,AnimationController 监听并根据 Duration 等计算出当前的 Animation.value;
此外也可以通过 Tween 将 double 类型转化为其他的类型比如 Offset 等;
上述两种方式中 value 都是随着时间线性变化,而 Curve 可以与 CurveTween、AnimationController 等结合使 value 实现非线性的变化。
当随着时间变化,计算出当前的 Animation.value 时,便可以根据此值修改 Flutter Widget 的各个属性,从而实现动画的视觉效果。
从上图可以看到,Flutter 提供的动画 Widget 主要分为两大类:
ImplicitlyAnimatedWidget 隐式动画,关于动画的开始、停止等都封装在 Widget 内部,只要 Widget 前后传入的值不同便可以自动从 old 渐变到 new,内置的这些类主要以 AnimatedFoo 命名。
AnimatedWidget,显式动画,需要使用者自己创建 Animation(一般是 AnimationController)并通过其管理动画,此类 Widget 主要是监听 AnimationController 的值并刷新 Widget 的内容。
此类 Widget 主要有三种使用方式:
- 继承 AnimatedWidget
- 使用 AnimatedBuilder
- 使用各种内置的 AnimatedWidget 子类,一般以 FooTransition 命名。
对于 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,并允许其他对象监听二者的变化。
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。
@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 值和的二分之一,值为 doubleAnimationMax<T extends num>
返回 first 和 next 中最大值AnimationMin<T extends num>
返回 first 和 next 中最小值
AnimationController
A controller for an animation.
尽管有各种子类,但 Animation 最常用的子类是 AnimationController,使用者可以用它来控制、监听动画、创建其他动画。
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
开始动画,此类动画还可以改变Duration和Curve:
- 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 并发出通知:
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 并发出通知。
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 主要是在一维空间对物理进行位置、速度等建模。
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
_InterpolationSimulation(this._begin, this._end, Duration duration, this._curve, double scale){...}
其 x() 方法中除了 t 为 0.0 或 1.0 的情况外,其余时候依靠 Curve(默认为 Curves.linear)计算值。
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
_RepeatingSimulation(double initialValue, this.min, this.max, this.reverse, Duration period, this.directionSetter){}
没有 Curve,其 double x(double timeInSeconds) 方法可以自动判断是否需要反向并修改方向(会触发 status 改变通知):
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 方法可以停止动画
参考资料
AnimationController api.flutter.dev