Flutter中的动画(一)

动画概述

对于任何UI框架,实现动画的原理都是一样的,通过画面在短时间内的变化形成动画,基本上人眼对30帧以上的动画的感受就是极限了,即在30帧以上人眼是基本区别不了的,在Flutter中,动画的平均帧率是可以达到60FPS的,这和原生基本持平,也是Flutter的一个特点。

主要涉及到的类

在Flutter中,实现动画主要涉及到四个类,分别是Animation,Curved,Tween,Controller。

其中Animation类是用来保存动画的插值和状态的

Curved是用来实现动态的变化方式,比如说一般变化是线性的,你可以通过设置不同Curved来实现不同的函数变化方式。

Tween,在默认情况下,AnimationController对象的值范围是在0.0到1.0之间,我们可以通过设置Tween来设置动画的值的变化范围以达到我们想要的效果。

Controller,用来控制动画的控制器。

Animation

Animation对象本身和UI渲染没有关系,它是用来保存动画插值和状态的对象,我们可以查看Animation源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
  abstract class Animation<T> extends Listenable implements ValueListenable<T> {

const Animation();

@override
void addListener(VoidCallback listener);


@override
void removeListener(VoidCallback listener);


void addStatusListener(AnimationStatusListener listener);

void removeStatusListener(AnimationStatusListener listener);

AnimationStatus get status;

@override
T get value;

bool get isDismissed => status == AnimationStatus.dismissed;

bool get isCompleted => status == AnimationStatus.completed;


@optionalTypeArgs
Animation<U> drive<U>(Animatable<U> child) {
assert(this is Animation<double>);
return child.animate(this as dynamic);
}

@override
String toString() {
return '${describeIdentity(this)}(${toStringDetails()})';
}


String toStringDetails() {
assert(status != null);
String icon;
switch (status) {
case AnimationStatus.forward:
icon = '\u25B6'; // >
break;
case AnimationStatus.reverse:
icon = '\u25C0'; // <
break;
case AnimationStatus.completed:
icon = '\u23ED'; // >>|
break;
case AnimationStatus.dismissed:
icon = '\u23EE'; // |<<
break;
}
assert(icon != null);
return '$icon';
}
}

Animation对象是一个在一段时间内一次生成一个区间(Tween)之间值的类,这种输出可以是线性的,曲线的(我们可以通过Curved来设置),这个类中还为我们定义了一些监听器和状态监听器,我们可以通过设置监听器来监听动画发生的一些变化,然后做出一些改变。

  1. addListener()可以给Animation添加帧监听器,在每一帧都会被调用。帧监听器中最常见的行为是改变状态后调用setState()来触发UI重建。
  2. addStatusListener()可以给Animation添加“动画状态改变”监听器;动画开始、结束、正向或反向(见AnimationStatus定义)时会调用StatusListener。

Tween

刚刚提到了Animation是用来生成在Tween之间的值的对象。

我们先来查看一下源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Tween<T extends dynamic> extends Animatable<T> {

// Tween的构造函数,接受一个开始的泛型和结束的泛型
// 正如我们先前提到的Tween是一个范围
// 这里的范围可以是数值也可以使某种对象例如Color等
Tween({ this.begin, this.end });

// 泛型的开始
T begin;

// 泛型的结束
T end;

// 我们需要传入一个进度百分比
// 这个函数就能直接返还给我们当前进度的插值
@protected
T lerp(double t) {
assert(begin != null);
assert(end != null);
return begin + (end - begin) * t;
}

// 该transform封装了lerp函数
// 就是传入的值为0直接返回begin,为1返回end,如果不是那就返回lerp函数返回的值
// 所以要实现自定义lerp,我们需要重写lerp函数而不是transform
@override
T transform(double t) {
if (t == 0.0)
return begin;
if (t == 1.0)
return end;
return lerp(t);
}

@override
String toString() => '$runtimeType($begin \u2192 $end)';
}

因为Tween继承Animatable这个抽象类,这个抽象类中有一个方法animate方法,它用来返回一个Animation对象,所以对于Tween类我们通常设置完begin和end之后会调用原本继承于Animatable抽象类的animate方法设置一个Animation对象并赋值给一个Animation对象。

Curve

Curve这个类是用来设置动画的变化曲线的,默认情况下是线性变化的。

我们一般使用CurveAnimation这个对象来自定义设置动画曲线,我们可以查看一下它的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> {

CurvedAnimation({
@required this.parent,
@required this.curve,
this.reverseCurve,
}) : assert(parent != null),
assert(curve != null) {
_updateCurveDirection(parent.status);
parent.addStatusListener(_updateCurveDirection);
}

// 用来设置父控制器,这里一般传入AnimationController
// 用来将自己的curve添加到这个控制器中去。
@override
final Animation<double> parent;

// 设置自己的curve
Curve curve;

// 用来设置动画的反转时的曲线,不设置就和原来相等,
// 为了保持动画在结束和反转开始的时候动画的连续性
// flutter会为我们将反转曲线的值和结束设置在同一点。
Curve reverseCurve;

// 用来设置曲线的方向
// 只有动画结束或者开始的时候我们才能设置
// 以免造成动画的不连续性
AnimationStatus _curveDirection;

// 更新curveDirection
void _updateCurveDirection(AnimationStatus status) {
switch (status) {
case AnimationStatus.dismissed:
case AnimationStatus.completed:
_curveDirection = null;
break;
case AnimationStatus.forward:
_curveDirection ??= AnimationStatus.forward;
break;
case AnimationStatus.reverse:
_curveDirection ??= AnimationStatus.reverse;
break;
}
}

bool get _useForwardCurve {
return reverseCurve == null || (_curveDirection ?? parent.status) != AnimationStatus.reverse;
}

@override
double get value {
final Curve activeCurve = _useForwardCurve ? curve : reverseCurve;

final double t = parent.value;
if (activeCurve == null)
return t;
if (t == 0.0 || t == 1.0) {
assert(() {
final double transformedValue = activeCurve.transform(t);
final double roundedTransformedValue = transformedValue.round().toDouble();
if (roundedTransformedValue != t) {
throw FlutterError(
'Invalid curve endpoint at $t.\n'
'Curves must map 0.0 to near zero and 1.0 to near one but '
'${activeCurve.runtimeType} mapped $t to $transformedValue, which '
'is near $roundedTransformedValue.'
);
}
return true;
}());
return t;
}
return activeCurve.transform(t);
}

@override
String toString() {
if (reverseCurve == null)
return '$parent\u27A9$curve';
if (_useForwardCurve)
return '$parent\u27A9$curve\u2092\u2099/$reverseCurve';
return '$parent\u27A9$curve/$reverseCurve\u2092\u2099';
}
}

对于CurveAnimation我们可以直接使用构造方法来指定父controller和想要实现的curve

AnimationController

用来实现动画的控制器,它包含动画的启动forward()、停止stop() 、反向播放 reverse()等方法。AnimationController会在动画的每一帧,就会生成一个新的值。默认情况下,AnimationController在给定的时间段内线性的生成从0.0到1.0(默认区间)的数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
AnimationController({
double value,
// 接受一个Duration对象,可以设置持续时间
this.duration,
this.debugLabel,
// 设置动画值最低
this.lowerBound = 0.0,
// 设置最高
this.upperBound = 1.0,
this.animationBehavior = AnimationBehavior.normal,
@required TickerProvider vsync,
}) : assert(lowerBound != null),
assert(upperBound != null),
assert(upperBound >= lowerBound),
assert(vsync != null),
_direction = _AnimationDirection.forward {
_ticker = vsync.createTicker(_tick);
_internalSetValue(value ?? lowerBound);
}

Ticker

当创建一个AnimationController时,需要传递一个vsync参数,它接收一个TickerProvider类型的对象,它的主要职责是创建Ticker,定义如下:

1
2
3
4
abstract class TickerProvider {
//通过一个回调创建一个Ticker
Ticker createTicker(TickerCallback onTick);
}

Flutter应用在启动时都会绑定一个SchedulerBinding,通过SchedulerBinding可以给每一次屏幕刷新添加回调,而Ticker就是通过SchedulerBinding来添加屏幕刷新回调,这样一来,每次屏幕刷新都会调用TickerCallback。使用Ticker(而不是Timer)来驱动动画会防止屏幕外动画(动画的UI不在当前屏幕时,如锁屏时)消耗不必要的资源,因为Flutter中屏幕刷新时会通知到绑定的SchedulerBinding,而Ticker是受SchedulerBinding驱动的,由于锁屏后屏幕会停止刷新,所以Ticker就不会再触发。

通过将SingleTickerProviderStateMixin添加到State的定义中,然后将State对象作为vsync的值,这在后面的例子中可以见到。

使用例子

这里我们实现一个Icon通过动画的值来变化大小和颜色的动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
class AnimateLove extends StatefulWidget {
@override
_AnimateLoveState createState() => _AnimateLoveState();
}

class _AnimateLoveState extends State<AnimateLove> with TickerProviderStateMixin {

// 用来保存动画的插值
Animation<double> _animation;
// 动画的控制器
AnimationController _animationController;
// 动画实现曲线效果
CurvedAnimation _curvedAnimation;
// 用来保存动画的值(这里则是Color)
Animation<Color> _colorTween;

@override
void initState() {
super.initState();
// 首先初始化动画控制器
// 这个类需要with一个TickerProviderStateMixin
// 这个类提供一个this实现动画的垂直同步
_animationController = AnimationController(
// 接受一个Duration,可以设置动画的持续时间
duration: Duration(seconds: 2),
// 设置垂直同步
vsync: this
);
// 初始化一个动画曲线
// 接受一个parent参数,这个是指定需要实现这个curve曲线的相应的动画控制器
// 接受的curve参数是具体实现了什么curve
_curvedAnimation = CurvedAnimation(parent: _animationController, curve: Curves.fastLinearToSlowEaseIn);
// Tween用来设置动画的插值
// 并且使用animate方法(参数是用来接受这个范围值的父Animation,通常为CurveAnimation)用来将范围值保存到一个Animation对象中
_colorTween = ColorTween(begin: Colors.red[200], end: Colors.red[800]).animate(_curvedAnimation);
// 这里也是设置一个动画保存值,这里是double
_animation = Tween(begin: 0.0, end: 100.0).animate(_curvedAnimation)
// 添加监听器,变化的时候重新设置UI渲染
..addListener((){
setState(() {});
// 增加状态监听器,这里接受一个带status状态参数的一个方法
})..addStatusListener((status){
// 这里实现的主要功能就是判断动画是否结束,结束就方向播放动画
if(status == AnimationStatus.completed) {
_animationController.reverse();
} else if (status == AnimationStatus.dismissed) {
_animationController.forward();
}
});

// 初始化设置动画向前开始
_animationController.forward();
}

@override
void dispose() {
// 销毁 减少资源浪费
_animationController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Center(
child: Icon(
Icons.favorite,
// 将数值赋给size
size: _animation.value,
// 将color赋值给color
color: _colorTween.value,
),
);
}
}
-------------本文结束感谢阅读-------------