9.1 动画
在大多UI框架中,动画实现大同小异,原理都相同的,就是在一段时间内快速多次改变外观,由于人眼存在视觉停留,所以最终看到的是一个连续的动画,这和看电影一样,我们将UI的一次更新成为一帧,对应屏幕刷新,而决定流畅度是重要因素就是FPS(Frame Per Second),就是每秒动画的帧数,显而易见,帧数约高动画越流畅!对于人眼来说帧率超过16,就比较流畅,超过30,就比较细腻,帧数大于30,人眼基本分辩不出来差别。由于每一帧都是UI改动的效果,那么60FPS,就是需要UI改动在16ms完成,持续的改变资源是比较耗费资源的,对设备性能要求比较高,所以UI系统中的动画的帧数的重要的性能指标,而在Flutter中,理想的情况下可以实现60FPS,这和原生基本一致。
Flutter中动画抽象
在Flutter中,官方对动画进行了抽象处理,主要包括Animation、Curve、Controller、Tween这四个角色,他们一起或者组合可以完成一个动画。
Animation
Animation本身是一个抽象类,他和渲染没有直接关系,而他的作用是保存动画的查值和状态;其中一个比较常见的是Animation <double>。Animation会在一个时间段内一次生成一个取件的值,Animation生成的值的曲线由Curve类来实现,可以是曲线或者跳跃性的都行。根据Animation对象的控制方式,动画可以正向运动(从开始到结束),也可以反向运行,甚至中间换方向或者其他操作。Animation哈还可以生成其他的比如Color、Offset。在动画的每一帧,我们可以在Animation取值来生成新的UI。
动画通知
我们可以通过AnimationController来监听动画的每一帧变化,有如下方法
- 'addListen': 可以给
Animation添加监听器,每一帧都会调用,可以借助setState()来刷新UI。 addStatusListener(): 可以给Animation添加状态监听器;动画开始、结束、。
Curve
动画过程可以是匀速、加速、突变的,Flutter通过Curve来实现曲线的变化。
可以通过CurveAnimation来是指定实现Curve的类,如:
_animation = CurvedAnimation(parent: _animationController,curve: Curves.bounceIn)
CurvedAnimation可以通过包装AnimationController和Curves.bounceIn生成一个动画对象,他们通过这种方式关联起来。我们指定动画曲线为Curves.bounceIn,他表示动画变化的效果,Curves是一个静态函数,官方做了很多类型的动画,常用的有:
| Curves 曲线 | 效果 |
|---|---|
| easInQuint | 慢速进,快速出 |
| linear | 匀速 |
| slowMiddle | 加速仅,中间平滑,后端加速 |
| fastOutSlowIn | 慢速进,快速出 |
Curve是个抽象类,我们也可以实现一个曲线,例如:
class SinLine extends Curve {
@override
double transform(double t) {
return sin(t * pi / 2);
}
}
animationController
animationController用于控制动画,他包含动画的启动、停止、重复、反向等方法。animationController会在动画的每一帧生成已新的值,默认情况下生成的值的范围是[0,1],例如:
final AnimationController _animationController = AnimationController(
vsync: this, duration: Duration(milliseconds: 1000));
生成的值的范围由属性lowerBound和upperBound来决定
final AnimationController controller = new AnimationController(
duration: const Duration(milliseconds: 2000),
lowerBound: 10.0,
upperBound: 20.0,
vsync: this
);
AnimationController是继承Animatin<double>,因此Animatin<double>可以使用的地方,AnimatinController都可以使用。
Ticker
当创建一个AnimationController时,需要传递一个vsync参数,他接受TickerProvider类型对象,他主要职责是创建Ticker,定义如下:
abstract class TickerProvider {
const TickerProvider();
Ticker createTicker(TickerCallback onTick);
}
Flutter在其启动时会绑定一个SchedulerBingding,通过SchedulerBingding可以给屏幕每次刷新添加回调,而Ticker通过SchedulerBingding来添加屏幕回调,这样的话,每次屏幕刷新都会回调用TickerCallback.使用Ticker(不是Timer)来驱动动画会防止屏外动画(动画超出屏幕)消耗不必要的资源,因为Flutter中屏幕刷新时会通知绑定的SchedulerBinding,而Ticker是受SchedulerBinding驱动的,所以锁屏后屏幕会停止刷新,所以Ticker不会触发。
通常将SingleTickerProviderStateMixin添加到State定义中,然后将state对象作为vsync的值,这在后面的例子可以见到。
Tween
默认情况下,AnimationController对象范围是[0,1],如果需要设置不同的范围或不同的类型,则可以使用Tween来生成不同的类型的值,如:
final Tween t = ColorTween(begin: Colors.green,end: Colors.red);
Tween不存储状态,只负责生成中间值,而他提供了evaluate(Animation<double> animation)方法,获取动画当前的值。Animation对象的当前值可以通过value()方法取到。evaluate函数还执行一些其它处理,例如分别确保在动画值为0.0
和1.0时返回开始和结束状态。
Tween是构造函数,需要begin和end两个参数,Tween的职责就是定义输入范围到输出范围的映射。通常范围为[0,1],也可以自定义范围。
Tween继承自Animatable<T>,而不是Animation<T>,Animatable主要是定义动画的值的规则。
我们看下SizeTween将动画范围映射两个面积之间的过度的例子。
final Tween size = SizeTween(begin: Size.zero,end: Size(100,100));
Tween.animate
使用Tween对象,需要调用animate()方法,然后传入一个控制器对象,例如:以下代码在1000毫米内生成从1到1000的整数值。
final AnimationController _animationController = AnimationController(
vsync: this, duration: Duration(milliseconds: 1000));
final Animation<int> _animation =
IntTween(begin: 0, end: 1000).animate(_animationController);
IntTween返回的是
Animation,而不是Animatable.
下边实例一个利用曲线生成一个颜色渐变的实例:
/// 在1000毫秒内生成值
final AnimationController _animationController = AnimationController(
vsync: this, duration: Duration(milliseconds: 1000));
/// 生成值是由红色渐变为绿色
final Animation _animation =
ColorTween(begin: Colors.red, end: Colors.green)
.animate(_animationController);
/// 生成的曲线规则是正向是 Curves.bounceInOut,逆向是Curves.linear
final Animation curv = CurvedAnimation(
parent: _animation,
curve: Curves.bounceInOut,
reverseCurve: Curves.linear);