看完之后,你就能告诉别人哪个类实现了Flutter动画

437 阅读9分钟

本文翻译自👉Animations overview,介绍了Flutter 动画相关的类,这些类的相互配合才构成了整个 Flutter 的动画大厦。


Flutter 的动画体系是建立在 Animation类之上的。Widget 可以读取动画当前的值,也可以监听状态的改变,然后将这些信息直接合并应用自己的build方法中,Widget 就动起来了。Widget 也能将自己的动画传递给其他 Widget ,其他 Widget 使用传递的动画作为更高级动画的基础。

Animation

Animation 类很重要,是动画框架实现的主力。动画就是一个特定类型的值,这个值可以在动画的生命周期内改变,简单来说,动画就是时间与值的对应关系。大多数动画 Widget,都接受一个 Animation 类型的对象作为其参数,Widget 就从这个对象中读取当前动画的值,然后响应这个值的变化。

addListener

只要动画的值发生了改变,动画就会通知所有的监听者。调用动画的addListener方法,就可以成为动画的监听者。一般来说,[State]对象会在动画发生改变的时候,在监听的方法体中,调用 [setState] 方法,Widget 就会使用新的动画值来重新构建。伪代码如下:

Animation<T> animation;
@override
void initState() {
  super.initState();
  animation.addListener((){
    setState(() {
      
    });
  });
}

这种模式非常常见。当动画的值发生改变时,有两个 Widget 可以帮助其他 Widget 重新构建:AnimatedWidget 和 AnimatedBuilderAnimatedWidget 的使用场景主要是 为 StatelessWidget 添加动画效果,只需要继承自 AnimatedWidget,重写 build 方法就可以了。AnimatedBuilder 的使用场景主要是希望超长的build构建中某一部分具有动画效果。需要在构建 AnimatedBuilder 的时候传入一个 builder 方法。

addStatusListener

动画也提供了状态字段——AnimationStatus,来表示下一个时间段动画该怎么样,比如停止、继续、反转等等。只要动画的状态改变了,动画就会通知所有的状态监听者,可以调用动画的 addStatusListener 成为其监听者。一般动画是从 dismissed 开始执行的, dismissed 状态是动画的时间范围或者值的范围的开始。 比如,如果动画的范围是从 0.0 to 1.0,那么动画的值是 0.0 的时候,状态会变成 dismissed 。动画一般是前进的-——forward (从 0.0 到 1.0),但是也可以是反向的—— reverse (从 1.0 到 0.0)。最终,如果动画达到了范围的末端,动画就达到了 completed 状态。

AnimationController

在创建动画之前,需要先创建一个AnimationControllerAnimationController 有两个作用,第一,它继承自 Animation,本身就是动画,把它当作动画来传参。第二,它可以控制动画,比如动画的前进、停止。除了基本的动画用法之外,AnimationController 还可以使用物理模拟器来fling动画,让动画有物理仿真效果。比如 弹跳等等。

因为 AnimationController 本身就是 Animation的子类,而动画又是可以嵌套的,所以就可以在 AnimationController 的基础上构建其他的动画。比如,可以构建 ReverseAnimation 动画,实现镜像效果,可以构建特定曲线的 CurvedAnimation 动画。

Tweens

有时动画不是简单的0.0至1.0区间的值,那么开发者就可以使用Tween<T>了。Tween<T> 可以在beginend 之间的过程值基础上进行计算,比如计算出颜色,计算出字符串等等,计算出的结果是补间值,或者效果值。补间值可以是指定类型的。比如 ColorTween 就是颜色的值。RectTween 就是矩形的值。开发者也可以自定义补间值,只需要继承自 Tween ,重写 lerp 方法。

Tween<T> 只负责根据动画过程0-1产生的中间值,来生成真正的效果值。为了得到当前帧的这个中间值,开发者就需要想办法得到这个值,addListener可以得到这个中间值。有两种方法可以将Tween<T> 与动画结合起来以获得一个具体的补间值:

  1. 直接根据当前的动画值计算补间值。这种方法适合已经监听了动画的 Widget(addListener),在监听中来重新构建。

  2. 在动画的基础上对补间值进行二次 animate 动画。 animate 方法返回一个新的补间动画,而不是返回单个值。这种方法适用于给其他 Widget(A) 创建一个新的动画,这个 A Widget 读取和监听当前的补间值。就是 A 不关心原始的值是什么,只关心原始对应的补间值是什么。比如 A 只关心颜色是红色的时候,它显示绿色,它在红色的基础上进行计算。

动画架构

动画实是建立在一些核心构建块基础之上的。

Scheduler

[SchedulerBinding]是一个单例类,暴漏了最原始的Flutter调度过程。

对于动画来说,最关键的是帧调度。每一帧都需要展示的屏幕上,Flutter’s 引擎会触发 begin frame 回调,调度器会把这个回调发送给每一个回调监听者。可以调用scheduleFrameCallback()方法注册成为监听者。 所有注册的监听者,都会收到 Duration 形式的帧的时间戳。因为注册者的获得的时间戳是相同的,所以它们触发的动画看起来也是同步的,差距的话,也可能是仅仅几毫秒。

Tickers

Ticker 衔接了调度器的 scheduleFrameCallback() ,会在每一个 tick 调用自己的回调。

Ticker 可以开始也可以停止。开始的时候会产生一个 Future ,结束的时候会计算出 Future 的结果。

每一次 tick,Ticker 都会调用一次回调,并且会调的入参是第一次 tick 到现在持续的时长。

因为 ticker 传给回调的时间戳是相对于第一个 tick 的时长,所以所有的tickers都是同步的。 如果我们在两个 tick 之间开启了第三个,它们的回调的参数的时间也是一样的。就像公交车站, 所有的 ticker 都在等待一个定期发生的事件(tick),去开始移动(计时)。

Simulations

Simulation 是一个抽象类,将一个时间值转为一个double值,并且有完成的概念。

原则上,Simulation 是无状态的,但是在实操中,一些 simulation 会在被查询的时候不可逆的改变状态,比如 BouncingScrollSimulation 和  ClampingScrollSimulation

various concrete implementations 中举了一些例子来展示不同的效果。

Animatables

Animatable 是抽象类,将一个 double 映射为一个特定的类型。并且 Animatable 是无状态的不可变的。

Tweens

Tween<T> 将 double 映射为一个特定的类型,比如颜色等等,它是 Animatable ,在 lerp 方法中,完成 double 到特定类型的转换。

Tween 是无状态的不可变的。

Composing animatables

Animatable<double> A 传递给 Animatable<double> Bchain() 方法,这样会创建一个新的 Animatable C,那么 C 的效果就是:原始的 double 先映射成 A, 然后 A 再映射成 B。

Curves

Curve 是抽象类,作用是将一个 0.0-1.0 范围内的 double 值,转为一个 0.0-1.0 范围内的 double 值。也是无状态的不可变的。它的作用就是定义动画的曲线。

Animations

Animation 是抽象类,定义了一些动画的协议接口:获取指定类型动画的值,动画的方向。动画的状态。动画值变化的监听者。

有一些子类的动画值是不会变化的,比如 kAlwaysCompleteAnimationkAlwaysDismissedAnimationAlwaysStoppedAnimation等等。这些动画的监听者也不会被调用。

一些 Animation 子类是无状态的,只是将监听者传给它们的父动画。有些是有状态的。

Composable animations

大多数 Animation 动画都接受一个显式的父动画 Animation<double> 。这些动画是被父动画驱动的的。

CurvedAnimation 的入参是:一个父动画 Animation<double>、正向的曲线 Curve ,反向的曲线 Curve 。CurvedAnimation 使用曲线来计算父动画的输入,将结果作为自己的动画效果。CurvedAnimation 是不可变的无状态的。

ReverseAnimation 的入参:一个父动画 Animation<double>,它会将父动画反转。比如父动画0.2对应的值是3,0.8对应的值是6。那么ReverseAnimation 动画0.2对应的值是6,0.8对应的值是3,就像镜子一样。CurvedAnimation 是不可变的无状态的。

ProxyAnimation 的入参:一个父动画 Animation<double>,仅仅是转发父动画当前的值。父动画是可变的。

TrainHoppingAnimation 动画有两个父动画,当动画交叉的时候,会在它们之间切换。

Animation controllers

AnimationController 是一个有状态的 Animation<double> ,它内部会持有一个 Ticker ,通过 Ticker 有了生命周期:开始和停止Ticker 的每一次回调,这个回调我们暂且叫为嘀嗒 ,每嘀嗒一下,AnimationController 都会执行下面的流程: 第一步,嘀嗒的参数中获取时间参数。第二步,将时间参数传给 Simulation ,并从 Simulation 获得动画的效果值。在这个过程中,如果 Simulation 上报时间已经到了,动画到了动画的持续时间,AnimationController 就会停止自己。

AnimationController

AnimationController 需要动画的上下界和动画持续时间。

在一些简单的例子中,比如 forward() 或者 reverse()AnimationController 在给定的时间内,在上下届范围内插一系列的值。

如果使用 repeat()AnimationController 也会插入一系列的值,只是不会停止,会重复。

如果使用 animateTo()AnimationController 会在给定的时间范围内,从当前的动画值到给定的目标值之间,插一系列的值。如果没有给定时间范围,就是 duration 是 null,那么AnimationController 会用默认的动画时间和动画默认的上下届来确定动画的速度。

如果使用 fling(),会用 Force 创建动画的模拟器 Simulation ,使用模拟器来驱动 AnimationController

如果使用 animateWith(), 那么会用给定的模拟器 Simulation 来驱动 AnimationController

上面介绍的方法都会返回一个 future,这个 future 就是 Ticker 提供的。当AnimationController 停止或者改变模拟器的时候, future 就会返回结果。

总结