第7节:详解Animation-2动画组

[TOC]

详解Animation-2动画组

参考文章:

一、Animation动画组-串行

参考文档:

串行动画的实现方案总共有三种,分别是 监听状态法, Interval时间间隔法, TweenSequence动画序列法.

1、监听状态法

状态监听法主要通过AnimationController监听动画的completed状态,然后再去执行下一个动画,如此往复,直到所有动画完成.

例如现在我们需要实现先执行组件在0.3秒钟往下偏移50个单位,然后再执行在0.6s中组件的颜色由 橘色 变为 红色.

整体Demo代码如下所示:

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
class FlutterAnimationWidget extends StatefulWidget {
TurnTableLotteryPage({Key key}) : super(key: key);

@override
_FlutterAnimationWidgetState createState() => _FlutterAnimationWidgetState();
}

class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
// ①声明位移动画控制器和颜色动画控制器以及位移动画和颜色动画
AnimationController _animationController;
Animation<double> _animation;
AnimationController _colorAnimationController;
Animation<Color> _colorAnimation;

@override
void initState() {
super.initState();

// ②分别创建位移、颜色的动画控制器和动画
_animationController = AnimationController(duration: Duration(milliseconds: 300), vsync: this);
// Tween对象调用`animate()`函数直接传入上面的AnimationController
_animation = Tween<double>(begin: 0, end: 50).animate(_animationController)
..addListener(() {
setState(() {});
});
_colorAnimationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
_colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(_colorAnimationController)
..addListener(() {
setState(() {});
});

// ③最后,我们只需要监听位移动画完成状态之后执行颜色动画即可,
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_colorAnimationController.forward();
};
});
}

void startEasyAnimation() {
_animationController.forward();
}

@override
void dispose() {
_animationController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
height: 50,
color: _colorAnimation.value,
margin: EdgeInsets.only(top: _animation.value),
),
FlatButton(
onPressed: startEasyAnimation,
child: Text(
"点击执行最简单动画",
style: TextStyle(color: Colors.black38),
),
),
],
),
),
);
}
}

2、Interval时间间隔法

上面的状态监听需要一个动画过程就写一个Controller,而且基本上还要每一个Controller都监听执行完成然后再去启动下一个Controller.如果一个动画过程有十几个,自己想想都是脑瓜子嗡嗡的.所以接下来我们就来介绍第二种方案 - Interval时间间隔法 .

Interval时间间隔法 的整体思路是一个动画Controller控制所有动画的执行.然后每一个动画只需要确认自己在整个动画的时间比重即可.

整体Demo代码如下所示:

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
class FlutterAnimationWidget extends StatefulWidget {
TurnTableLotteryPage({Key key}) : super(key: key);

@override
_FlutterAnimationWidgetState createState() => _FlutterAnimationWidgetState();
}

class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
// ①声明一个动画Controller和多个动画.
AnimationController _animationController;
Animation<double> _animation;
Animation<Color> _colorAnimation;

@override
void initState() {
super.initState();

// 然初始化AnimationController,AnimationController的动画时间(**duration**)要设置成所有动画的总时长,例如这里我设定为600毫秒(_animation时长:300毫秒,_colorAnimation时长:300毫秒).
_animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
// Tween对象调用`animate()`函数不再是直接传入上面的AnimationController,而是传入一个 CurvedAnimation 对象。
// 其中CurvedAnimation构建过程中需要传入两个参数一个是 parent ,用于指定AnimationController. 另外一个是 curve,用于指定动画曲线函数.我们可以使用常用的动画曲线函数,也可以自己生成,这里我们就自己生成.指定动画执行的时间区间.
_animation = Tween<double>(begin: 0, end: 50).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(0.0, 0.5), // 由于两个动画时间长度是对分的,每一个都是300毫秒,所以 curve 参数中的值就分别是 **Interval(0.0, 0.5)**、**Interval(0.5, 1.0)**
),
)..addListener(() {
setState(() {});
});
_colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(0.5, 1.0),// 由于两个动画时间长度是对分的,每一个都是300毫秒,所以 curve 参数中的值就分别是 **Interval(0.0, 0.5)**、**Interval(0.5, 1.0)**
),
)..addListener(() {
setState(() {});
});
}

void startEasyAnimation() {
_animationController.forward();
}

@override
void dispose() {
_animationController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
height: 50,
color: _colorAnimation.value,
margin: EdgeInsets.only(top: _animation.value),
),
FlatButton(
onPressed: startEasyAnimation,
child: Text(
"点击执行最简单动画",
style: TextStyle(color: Colors.black38),
),
),
],
),
),
);
}
}

3、TweenSequence动画序列法

上面的两种方案虽然能解决动画组的问题,但是都太过于繁琐,那么有没有一种比较优雅的方案呢?这就需要使用到 TweenSequenceTweenSequenceItem 这两个类了. 其中 TweenSequence 是动画组类,TweenSequenceItem 则是用来定义每一个动画的具体实现的类.但是TweenSequenceTweenSequenceItem也不是尽善尽美的,它最大的问题就是前后变化的属性值类型必须是一致的.

下面,我们仍然以改变Margin为例, 先让视图组件往上移动50,再让视图 来看看如何使用 TweenSequenceTweenSequenceItem 来实现这个动画.

整体代码如下所示:

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
class FlutterAnimationWidget extends StatefulWidget {
TurnTableLotteryPage({Key key}) : super(key: key);

@override
_FlutterAnimationWidgetState createState() => _FlutterAnimationWidgetState();
}

class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
// ①声明一个 动画控制器AnimationController 和 动画Animation.
AnimationController _animationController;
Animation<double> _animation;

@override
void initState() {
super.initState();
// 仍然以两者的动画总时长为600毫秒为例
_animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);

// 然后,我们通过 **TweenSequence** 和 **TweenSequenceItem** 这两个类对 动画Animation 进行实现.
// 实现两个 **TweenSequenceItem**, **TweenSequenceItem**中的 <font color=red>weight</font> 属性是来设定动画执行的时间权重.也就是在整个动画过程,当前动画执行时长占总时长的比例.例如下面 第一个动画插值占的时间比例为 `50/(50 + 100)`. 第二个动画插值占的时间比例为 `100/(50 + 100)` .
TweenSequenceItem downMarginItem = TweenSequenceItem<double>(
tween: Tween(begin: 1.0, end: 50.0),
weight: 50,
);
TweenSequenceItem upMarginItem = TweenSequenceItem<double>(
tween: Tween(begin: 50.0, end: 100.0),
weight: 100,
);
// 然后创建一个动画插值组,把上面两个动画插值放入组中.
TweenSequence tweenSequence = TweenSequence<double>([
downMarginItem,
upMarginItem,
]);
// 最后,生成动画就OK了.
_animation = tweenSequence.animate(_animationController);
_animation.addListener(() {
setState(() {});
});
}

void startEasyAnimation() {
_animationController.forward();
}

@override
void dispose() {
_animationController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
height: 50,
color: Colors.orangeAccent,
margin: EdgeInsets.only(top: _animation.value),
),
FlatButton(
onPressed: startEasyAnimation,
child: Text(
"点击执行最简单动画",
style: TextStyle(color: Colors.black38),
),
),
],
),
),
);
}
}
动画组实现总结

上面三种实现动画组基本上已经说完了,接下来我们就来对比其不同点.

特性 监听状态法 Interval时间间隔法 TweenSequence动画序列法
代码简洁度 🔅🔅 🔅🔅🔅 🔅🔅🔅🔅🔅
动画是否可交织
动画属性是否可以多变

动画是否可交织 : 动画可否交织主要是说两个动画之间是否需要上一个动画完全执行完成之后,下一个动画才能执行.

动画属性是否可以多变 : 动画属性多变是指当前动画过程中可变化的属性是否可以有多个,例如同时变化尺寸和颜色等等.