Flutter中的动画系统基于Animation对象的类型。控件可以直接通过读取其当前的值并监听其状态变化来将这些动画添加到其构建函数中,或者可以使用动画作为更精细的动画的基础,并将它们传递给其他控件。
动画系统的主要构建块是Animation类。动画表示可以在动画生命周期内改变的特定类型的值。执行动画的大多数控件都会接收一个Animation对象作为参数,从中读取动画的当前值,并监听对象对该值的更改。
每当动画的值更改时,动画通知所有添加了addListener的监听器。通常,监听动画的State对象将在其监听器回调中调用setState,以通过动画的新值向控件系统通知其需要重构。
这种模式是很常见的,当动画更改值时,有两个控件帮助控件重建:AnimatedWidget和AnimatedBuilder。第一个,AnimatedWidget,对于无状态的动画控件最有用。要使用AnimatedWidget,只需将其子类化并实现构建函数即可。第二个,AnimatedBuilder,对于更复杂的控件很有用,可以将动画作为其构建函数的一部分。要使用AnimatedBuilder,只需构建控件并传递一个builder函数。
动画还提供了一个AnimationStatus,它指示动画随着时间的推移如何演变。每当动画的状态发生变化时,动画通知所有添加了addStatusListener的监听器。通常动画从dismissed状态开始,这意味着它们处于其范围的开始。例如,从0.0到1.0的动画为dismissed时,其值为0.0。然后,动画可以运行forward(比如从0.0到1.0)或者reverse(比如从1.0到0.0)。最终,如果动画达到其范围的最终值(比如1.0),则动画达到completed状态。
要创建动画,首先创建一个AnimationController。除了作为动画本身,AnimationController可以让您控制动画。例如,您可以告诉控制器forward(向前播放)或stop(停止)动画。您还可以fling(投掷)动画,其使用诸如弹簧的物理模拟来驱动动画。
创建动画控制器后,就可以基于它开始构建其他动画。例如,可以创建一个ReverseAnimation,反映原始动画,但反向运行(比如从1.0到0.0)。类似地,还可以创建一个CurvedAnimation,曲线动画,其值由curve调整。
动画在0.0到1.0的区间,可以使用Tween,它在其begin(开始)和end(结束)值之间进行插值。许多类型具有特定的Tween子类,提供类型特定的插值。例如, ColorTween在颜色之间进行插值,并在矩形之间插入RectTween。您可以通过创建自己的Tween子类并覆盖其lerp函数来定义自己的插值。
补间只是定义了如何在两个值之间插值。要获取动画当前帧的具体值,您还需要一个动画来确定当前状态。将补间动画与动画结合起来有两种方法,获得一个具体的值:
使用evaluate获取动画当前值的补间。这个方法对于已经在监听动画的控件最为有用,因此每当动画改变值时重建。
使用animate为补间动画添加动画。而不是返回单个值,animate函数返回一个包含补间的新动画。当您想要将新创建的动画提供给另一个控件时,此方法最为有用,然后可以读取包含补间的当前值以及监听对值的更改。
动画实际上是由许多核心构建块构建的。
SchedulerBinding是暴露Flutter调度原语的单例类。
在这个讨论中,关键的原语是帧回调。每当需要在屏幕上显示一个帧时,Flutter的引擎会触发一个“开始帧”回调,调度器将复制到所有使用scheduleFrameCallback()注册的监听器。所有这些回调都是以某个任意时期的Duration(持续时间)的形式给出该帧的官方时间戳。由于所有回调具有相同的时间,所以从这些回调触发的任何动画都将完全同步,即使它们需要几毫秒来执行。
Ticker类挂接到调度程序的scheduleFrameCallback()机制中,以便每个刻度调用回调。
Ticker可以启动和停止。启动时,它返回一个将在停止时解决的Future。
每个刻度,Ticker提供回调与开始之后第一个刻度的持续时间。
因为在启动之后,代码总是给予相对于第一个刻度的经过时间,所有的代码都是同步的。如果您在两帧之间的不同时间启动三个刻度,则它们将全部同步于相同的起始时间,并随后将被锁定。
Simulation抽象类将相对时间值(经过时间)映射到双精度值,并具有完成的概念。
原则上模拟是无状态的,但在实践中,一些模拟(例如,BouncingScrollSimulation和ClampingScrollSimulation)在查询时不可逆地改变状态。
有简单的一维物理模拟,如弹簧、摩擦和重力等不同效果的Simulation类的各种具体实现。
Animatable抽象类将一个双精度值映射到一个特定类型的值
Animatable类是无状态和不可变的。
Tween抽象类将在0.0-1.0范围内的双精度值映射到类型值(例如,Color或另一个双精度值),是一个Animatable。
它具有输出类型(T),该类型的begin(起始)值和end(结束)值的概念,以及在给定输入值的起始值和结束值之间插值(lerp)的方式(双精度值范围在0.0-1.0内)。
Tween类是无状态的和不可变的。
将Animatable(父类)传递给动画的chain()方法会创建一个新的Animatable子类,该子类应用父映射,然后应用子映射。
Curve抽象类映射在0.0-1.0范围内翻倍,以0.0-1.0的范围翻倍。
Curve类是无状态的和不变的。
Animation抽象类提供给定类型的值、动画方向和动画状态的概念,以及用于注册在值或状态更改时调用的回调的监听器接口。
Animation的一些子类具有从不改变的值(kAlwaysCompleteAnimation, kAlwaysDismissedAnimation, AlwaysStoppedAnimation);在这些上注册回调没有任何效果,因为从未调用回调。
Animation变体是特别的,因为它可以用于表示一个双精度值的范围0.0-1.0,这是Curve和Tween类预期的输入以及Animation的一些其他子类。
一些Animation子类是无状态的,只是将监听器转发给他们的父类,还有些是有状态的。
大多数Animation子类采用显式的父类Animation。它们由父类驱动。
CurvedAnimation子类将一个Animation类(父类)和一对Curve类(正向和反向曲线)作为输入,并使用父值作为曲线的输入来确定其输出。 CurvedAnimation是不可变的和无状态的。
ReverseAnimation子类将Animation类作为其父类,并反转动画的所有值。它假定父级使用的值在0.0-1.0范围内,并返回1.0-0.0范围内的值。父动画的状态和方向也相反。CurvedAnimation是不可变的和无状态的。
ProxyAnimation子类将Animation类作为其父类,并转发该父类的当前状态。但是父类是可变的。
TrainHoppingAnimation子类需要两个父类,并在它们的值跨越时切换它们。
AnimationController是有状态的Animation,它使用一个Ticker来赋予自己的生命。它可以启动和停止。每个刻度,它需要从它开始的时间经过,并将其传递给Simulation以获得一个值。那就是它报告的值。如果Simulation报告说,当时它已经结束,那么控制器就会自动停止。
动画控制器可以被赋予一个下限和上限的动画之间和持续时间。
在简单的情况下(使用forward(),reverse(),play()和resume()),动画控制器简单地从下限到上限进行线性插值(反之亦然)在给定的持续时间。
当使用repeat()时,动画控制器在给定的持续时间内使用给定范围之间的线性插值,但不会停止。
当使用animateTo()时,动画控制器在给定持续时间内从当前值到给定目标进行线性插值。如果该方法没有持续时间,则使用控制器的默认持续时间和由控制器的下限和上限描述的范围来确定动画的速度。
当使用fling()时,Force用于创建一个特定的simulation,然后用于驱动控制器。
当使用animateWith()时,给定的simulation用于驱动控制器。
这些方法都返回了Ticker提供的future,当控制器下次停止或更改simulation时,这些方法将会解决。
将Animation(新父类)传递给动画的animate()方法将创建一个新的Animation子类,其动作类似于Animatable,但是从给定的父类驱动。