什么是动画,从数学上来说,动画指的是一个属性的变换过程,实际上,就是一个函数,将一个属性值变成另一个属性值的过程。
从现实上来说,动画实际上就是将一系列静态的图片,在一定时间内快速切换,从而利用人眼的视觉暂留效应形成动态的画面。
对于现代移动设备来说,保持流畅体验的标准是60帧每秒,即每秒要切换60张静态图,这每一帧,在Flutter中被称之为Tick,也叫Vsync,在使用动画的时候,需要mixin的SingleTickerProviderStateMixin,就是Tick的一种,开发者可以监听Animation的每一帧回调,在回调中去刷新UI,从而实现动画的播放。
在Flutter中,包含两种动画类型,分别是Tween动画和Physics动画。
一个Tween实际上就是定义的属性值的变化区间,而基于Physics的动画,实际上也是一个变化区间,只不过它的变化区间是根据物理引擎计算出来的,更加模拟真实的物理效果。
Physics动画的相关类如下所示。
https://api.flutter.dev/flutter/animation/AnimationController/animateWith.html https://api.flutter.dev/flutter/physics/SpringSimulation-class.html
❝这两种动画类型,实际上是描述动画的两种方式,一种是通过区间值,另一种是通过模拟物理环境(胡克定律) ❞
首先来思考下,如何来设计一个动画框架——动画,简单来说,就是描述一个属性,将其起始值,经过多长时间,由怎样的变化速率,变成目标值,这样就完成了一次动画。
在Flutter中,上面的这些步骤是如何实现的呢?
- Tween,负责起始值到目标值的数据生成,可以是0-1,也可以是1-100,也可以是Red-Blue,总之就是数据的变化
- Curve,负责动画的变化速率,即作用在Tween的中间值上的函数f(x),避免生硬的动画过程
- AnimationController,负责整个动画的行进过程,即控制动画的开始、结束、循环,以及时长
那么有了这三个核心概念,在Flutter中描述动画就很简单了,通过Tween来描述动画的变化区间,在用AnimationController来管理Tween和Curve,就完成了动画的控制。
不过这里要注意的是,AnimationController是Animation的实现类,所以理论上来说,Flutter Animation的核心应该是Animation、Tween和Curve。
Animation在Flutter中与实际的UI渲染是没有任何关系的,它仅仅是一个数值发生器,和Android中的属性动画ValueAnimator非常类似。
下面,针对前面提到的几个名词来进行下分析,这些东西频繁出现在Flutter动画中,理清它们之间的关系和作用,才能为实现动画打好基础。
Animation与AnimationController
Animation是Flutter中实现动画的核心类,但它更像是一个数值发生器,用于产生指定泛型的数据,或者是根据Tween来生成变换后的区间数值,而整个Widget树,根据数值的更新来重绘,从而产生动画效果。
❝这也是申明式编程,实现动画更加简单方便的原因,因为它更符合动画的语义效果。 ❞
Animation是具有状态的对象,它保存了当前的映射值和当前的运行状态(动画完成、中断)等。所以,当动画Stop后继续播放,这个值是有状态的,它会从Stop的地方继续执行,除非你指定了from: 0。
Animation有两个重要的实现,分别是AnimationController和CurvedAnimation。
- AnimationController AnimationController是Animation的实现,在屏幕刷新的每一帧,AnimationController都会产生一个对应的数值,当不使用Tween时,AnimationController最基础的实现,就是连续的、线性的依次产生一个[0,1]的数值。如果需要改变AnimationController的输出范围,可以通过修改AnimationController的lowerBound、upperBound参数,并在调用时设置controller.forward(from: xxx)函数。
- CurvedAnimation CurvedAnimation也是Animation的实现,只不过它可以根据Curve曲线来生成非线性的区间值。CurvedAnimation并不能驱动动画,它只是一个数值的转换器。
前面提到了AnimationController是一个线性的数值发生器,因为它产生的数值的速率是相同的,但是很多情况下,动画的发生速率是变化的,例如加速或者减速效果,这在Android原生中,是通过Interpolator来实现的,而在Flutter中,可以通过CurvedAnimation来实现。
CurvedAnimation同样继承自Animation,所以它和AnimationController是同等地位的,但是通常情况下,CurvedAnimation的创建是需要AnimationController的。创建一个CurvedAnimation最核心的代码如下所示。
代码语言:javascript复制_curvedAnimation = CurvedAnimation(parent: _controller, curve: Curves.bounceIn);
其中parent参数就是需要指定的AnimationController,而curve参数,就是需要指定的插值器,在Curves中,已经内置了很多不同类型的插值器,基本覆盖了常用的使用场景,当然,开发者也可以自定义自己的插值器,只需要继承Curve类并实现transform函数即可。
Tween
Tween,是Animatable的子类,它的作用类似一个函数f(x),它将一个输入x(x的取值范围是[0,1]),经过f的变换,产生新的数值。
那么前面讲解了AnimationController,通过设置AnimationController的一些参数,就可以获得动画的值,那这里为什么还要讲解Tween呢?
Tween与Animation不同,Tween是一个无状态的对象,它只包含begin和end值。当AnimationController默认产生的[0,1]不能满足需求时,就可以通过Tween来生成不同的区间范围值,Tween不保存任何状态,它只是起始值的变换函数。
- Tween Tween不仅仅可以返回double类型的数据,在Flutter SDK中,系统定义了很多内置的Tween,当然,开发者也可以自定义自己的Tween。
- CurvedTween 与CurvedAnimation一样,Tween也有类似的CurvedTween
另外,这也是Flutter的一种解耦的方式,AnimationController的职责是产生原始的、统一的[0,1]数值,而具体的动画效果,通过交给相应的Tween-Animation来创建不同的动画类型,将AnimationController中的设置分配到了不同的Tween中,而AnimationController则负责管理这些Tween。
一个最简单的Tween动画,就是将[0,1]变换为[begin,end]。
Tween的类型
Tween有很多不同类型的实现,它们都继承自Animatable,如下图所示。
2dba725b185c4c96b0ec91d50c06a857
这里通过一个例子来介绍下Tween和ColorTween的使用方法,其它类型的Tween的使用基本类似,可以类推。核心代码如下所示。
代码语言:javascript复制_animation = Tween(begin: 0.0, end: 100.0).animate(_controller);
_animationColor = ColorTween(begin: Colors.red, end: Colors.blue).animate(_controller);
Tween的使用非常简单,创建Tween对象并设置相应参数后,通过animate函数关联相应的AnimationController即可(animate函数即将Animatable转换成了Animation)。
可以发现,实际上Tween的使用只是参数上的区别,不同类型的Tween,作用的属性不同,产生的值的类型也不同,这个值可以是具体数字、可以是Color、Offset,甚至是对象。
那么除了通过animate函数以外,还可以通过AnimationController.drive函数来加载一个Tween。从效果上来看AnimationController.drive == Tween.animate,是等效的,其实drive函数本身,也是通过animate来实现的。
Tween与chain
chain函数可以将多个Tween进行复合,一个常用的场景就是与CurveTween复合,给Tween添加插值曲线,这样就不用使用CurvedAnimation来创建Curve动画了,示例代码如下所示。
代码语言:javascript复制tween1.chain(CurveTween(curve: Curves.easeIn)).animate(animation)
Tween中叠加chain的原理,实际上就是函数的叠加,因为有时候,一个Tween是很难描述一个复杂的动画的,这个时候,就需要进行叠加了,类似数学中的复合函数g(f(h(x)))。
通过chain,就可以很方便的将复杂动画拆解成多个单一属性的简单动画的叠加,这样就会让动画开发的思路更加清晰。
自定义Tween
Tween表示的是动画的变换函数,Flutter预设了很多种不同的Tween来帮助开发者完成动画的创建,同时也给出了创建自定义Tween的方法,下面的代码就演示了如何创建一个自定义的Tween,来实现打字机的特效。
首先,需要定义一个Tween,用来不断的截取字符串,模拟出文字逐渐出来的效果,代码如下所示。
代码语言:javascript复制class TypewriterTween extends Tween<String> {
TypewriterTween({String begin = '', String end}) : super(begin: begin, end: end);
String lerp(double t) {
var cutoff = (end.length * t).round();
return end.substring(0, cutoff);
}
}
代码很简单,实际上就是根据动画的进度参数t来对String进行截取。
所以,对Tween的理解不要局限于数值的改变上,任何类型的「改变」,都可以作为Tween。
本质
让我们从Tween的实现上来看下Tween到底是什么东西。
image-20220409151640729
可以发现,Tween实际上就是一个lerp函数作用之后的数值,也就是说,我们通常使用:
代码语言:javascript复制Tween().animate(parent) .value
来获取动画值的方式,本质上和下面这种方式是一样的:
代码语言:javascript复制tween.evaluate(_controller)
从源码中,其实也能看出,如下所示。
image-20220409152251135
所以,Tween实际上是一个函数,用来告诉系统,begin到end,中间的每一个值的产生是如何进行的。
Curves
在动画的函数中,duration和curve两个非常重要的参数,duration控制的是动画的响应时长,而curve控制的是动画的响应曲线。
duration很好理解,就是动画持续时间,而curve,则是描述动画行进的曲线,默认情况下,动画以线性曲线行进,所以,动画的变换过程是线性的,以恒定不变的速率进行变换,这个过程,在动画中,被称为Interpolator,即插值器,插值器的变换过程,可以用一个函数图像来表示,所以默认的线性变换过程,用图来表示,就如下图所示。
65ee6af49b65fd77ebc2e608b65e0c81
在Flutter中,SDK定义了很多常用的插值器,具体的插值器可以在下面的网站上找到,如下所示。
https://api.flutter.dev/flutter/animation/Curves-class.html
e4adc8dd5e6fa3520a104cc4e786fa14
当一个线性插值器被替换成其它非线性插值器时,动画的行进过程就不是恒定不变的了,而是类似插值器的函数图像来进行变换。
运用插值器,可以让动画的实现过程变的更加符合自然规律、设计规范,让动画更加真实、更加具有美感。
CurvedAnimation是使用Curve的方法之一,另一个方法就是使用CurveTween。这两个方法也是等效的。
本质
我们来看下一个Curve是怎么定义的。
image-20220409150619284
可以发现,其实Curve和动画没有什么直接关系,它的作用就是让原本线性的值,根据某些函数进行转换,从而产生一些不是那么线性的值。
所以,不仅仅是在动画中可以使用Curve,在其它需要的计算场景下,也可以直接使用Curve来进行计算,代码如下所示。
代码语言:javascript复制Curves.elasticIn.transform(_controller.value)
上面的这些名词,就是Flutter动画的基石,只有深刻理解这些名词背后的含义,才能对动画的实现了如指掌。
向大家推荐下我的网站 https://xuyisheng.top/
专注 Android-Kotlin-Flutter 欢迎大家访问
本文原创公众号:群英传,授权转载请联系微信(Tomcat_xu),授权后,请在原创发表24小时后转载。
< END >
作者:徐宜生
更文不易,点个“三连”支持一下