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,并允许其他对象监听二者的变化。
1 | abstract class Animation<T> extends Listenable implements ValueListenable<T> { |
Animation 继承自 Listenable,实现 ValueListenable 接口,其他类可以通过 addListener/removeListener 或者 addStatusListener/removeStatusListener 监听 Animation 的 value 或者 status 变化。
Animation 共有 4 种状态:dismissed、forward、reverse、completed。
除此之外,Animation.drive 方法可以创建一个新的将传入的 Animatable 应用到自身的 Animation。
1 |
|
也就是说,提供了将 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,使用者可以用它来控制、监听动画、创建其他动画。
1 | 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 并发出通知:
1 | TickerFuture _startSimulation(Simulation simulation) { |
_ticker.start()
方法最终通过SchedulerBinding.instance.scheduleFrameCallback()
方法监听 Flutter Framework 的帧刷新,并回调 AnimationController._tick
方法
_tick
在此方法内部根据当前时间和_simulation 获取_value 并发出通知。
1 | void _tick(Duration elapsed) { |
其他方法
- void resync(TickerProvider vsync) 使用 vsync 重新创建新的_ticker
- void stop({ bool canceled = true }) 停止动画,不会触发通知,默认标记动画为 canceled
- void dispose() 释放资源,动画被标记为 canceled
Simulation
从上面的分析中,我们看到 Simulation 在 AnimationController 动画中也起到很重要的作用:Simulation 主要是在一维空间对物理进行位置、速度等建模。
1 | abstract class 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 | double x(double timeInSeconds) { |
_RepeatingSimulation
1 | _RepeatingSimulation(double initialValue, this.min, this.max, this.reverse, Duration period, this.directionSetter){} |
没有 Curve,其 double x(double timeInSeconds)方法可以自动判断是否需要反向并修改方向(会触发 status 改变通知):
1 | double x(double timeInSeconds) { |
此外比较特殊的是他的 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