跳到正文
Willh的个人博客 记录 · 思考 · 分享
《Flutter实战·第二版》

9.5 交织动画

有些时候我们可能会需要一些复杂的动画,这些动画可能由一个动画序列或重叠的动画组成,比如:有一个柱状图,需要在高度增长的同时改变颜色,等到增长到最大高度后,我们需要在X轴上平移一段距离。可以发现上述场景在不同阶段包含了多种动画,要实现这种效果,使用交织动画(Stagger Animation)会非常简单。交织动画需要注意以下几点:

  1. 要创建交织动画,需要使用多个动画对象(Animation)。
  2. 一个AnimationController控制所有的动画对象。
  3. 给每一个动画对象指定时间间隔(Interval)

所有动画都由同一个AnimationController (opens new window)驱动,无论动画需要持续多长时间,控制器的值必须在0.0到1.0之间,而每个动画的间隔(Interval)也必须介于0.0和1.0之间。对于在间隔中设置动画的每个属性,需要分别创建一个Tween (opens new window) 用于指定该属性的开始值和结束值。也就是说0.0到1.0代表整个动画过程,我们可以给不同动画指定不同的起始点和终止点来决定它们的开始时间和终止时间。

示例

下面我们看一个例子,实现一个柱状图增长的动画:

  1. 开始时高度从0增长到300像素,同时颜色由绿色渐变为红色;这个过程占据整个动画时间的60%。
  2. 高度增长到300后,开始沿X轴向右平移100像素;这个过程占用整个动画时间的40%。

我们将执行动画的Widget分离出来:

1class StaggerAnimation extends StatelessWidget {
2  StaggerAnimation({ Key key, this.controller }): super(key: key){
3    //高度动画
4    height = Tween<double>(
5      begin:.0 ,
6      end: 300.0,
7    ).animate(
8      CurvedAnimation(
9        parent: controller,
10        curve: Interval(
11          0.0, 0.6, //间隔,前60%的动画时间
12          curve: Curves.ease,
13        ),
14      ),
15    );
16
17    color = ColorTween(
18      begin:Colors.green ,
19      end:Colors.red,
20    ).animate(
21      CurvedAnimation(
22        parent: controller,
23        curve: Interval(
24          0.0, 0.6,//间隔,前60%的动画时间
25          curve: Curves.ease,
26        ),
27      ),
28    );
29
30    padding = Tween<EdgeInsets>(
31      begin:EdgeInsets.only(left: .0),
32      end:EdgeInsets.only(left: 100.0),
33    ).animate(
34      CurvedAnimation(
35        parent: controller,
36        curve: Interval(
37          0.6, 1.0, //间隔,后40%的动画时间
38          curve: Curves.ease,
39        ),
40      ),
41    );
42  }
43
44
45  final Animation<double> controller;
46  Animation<double> height;
47  Animation<EdgeInsets> padding;
48  Animation<Color> color;
49
50  Widget _buildAnimation(BuildContext context, Widget child) {
51    return Container(
52      alignment: Alignment.bottomCenter,
53      padding:padding.value ,
54      child: Container(
55        color: color.value,
56        width: 50.0,
57        height: height.value,
58      ),
59    );
60  }
61
62  
63  Widget build(BuildContext context) {
64    return AnimatedBuilder(
65      builder: _buildAnimation,
66      animation: controller,
67    );
68  }
69}

StaggerAnimation中定义了三个动画,分别是对Container的height、color、padding属性设置的动画,然后通过Interval来为每个动画指定在整个动画过程中的起始点和终点。下面我们来实现启动动画的路由:

1class StaggerRoute extends StatefulWidget {
2  
3  _StaggerRouteState createState() => _StaggerRouteState();
4}
5
6class _StaggerRouteState extends State<StaggerRoute>
7    with TickerProviderStateMixin {
8  late AnimationController _controller;
9
10  
11  void initState() {
12    super.initState();
13
14    _controller = AnimationController(
15      duration: const Duration(milliseconds: 2000),
16      vsync: this,
17    );
18  }
19
20  _playAnimation() async {
21    try {
22      //先正向执行动画
23      await _controller.forward().orCancel;
24      //再反向执行动画
25      await _controller.reverse().orCancel;
26    } on TickerCanceled {
27      // the animation got canceled, probably because we were disposed
28    }
29  }
30
31  
32  Widget build(BuildContext context) {
33    return Center(
34      child: Column(
35        children: [
36          ElevatedButton(
37            onPressed: () => _playAnimation(),
38            child: Text("start animation"),
39          ),
40          Container(
41            width: 300.0,
42            height: 300.0,
43            decoration: BoxDecoration(
44              color: Colors.black.withOpacity(0.1),
45              border: Border.all(
46                color: Colors.black.withOpacity(0.5),
47              ),
48            ),
49            //调用我们定义的交错动画Widget
50            child: StaggerAnimation(controller: _controller),
51          ),
52        ],
53      ),
54    );
55  }
56}
57
58class StaggerAnimation extends StatelessWidget {
59  StaggerAnimation({
60    Key? key,
61    required this.controller,
62  }) : super(key: key) {
63    //高度动画
64    height = Tween<double>(
65      begin: .0,
66      end: 300.0,
67    ).animate(
68      CurvedAnimation(
69        parent: controller,
70        curve: Interval(
71          0.0, 0.6, //间隔,前60%的动画时间
72          curve: Curves.ease,
73        ),
74      ),
75    );
76
77    color = ColorTween(
78      begin: Colors.green,
79      end: Colors.red,
80    ).animate(
81      CurvedAnimation(
82        parent: controller,
83        curve: Interval(
84          0.0, 0.6, //间隔,前60%的动画时间
85          curve: Curves.ease,
86        ),
87      ),
88    );
89
90    padding = Tween<EdgeInsets>(
91      begin: EdgeInsets.only(left: .0),
92      end: EdgeInsets.only(left: 100.0),
93    ).animate(
94      CurvedAnimation(
95        parent: controller,
96        curve: Interval(
97          0.6, 1.0, //间隔,后40%的动画时间
98          curve: Curves.ease,
99        ),
100      ),
101    );
102  }
103
104  late final Animation<double> controller;
105  late final Animation<double> height;
106  late final Animation<EdgeInsets> padding;
107  late final Animation<Color?> color;
108
109  Widget _buildAnimation(BuildContext context, child) {
110    return Container(
111      alignment: Alignment.bottomCenter,
112      padding: padding.value,
113      child: Container(
114        color: color.value,
115        width: 50.0,
116        height: height.value,
117      ),
118    );
119  }
120
121  
122  Widget build(BuildContext context) {
123    return AnimatedBuilder(
124      builder: _buildAnimation,
125      animation: controller,
126    );
127  }
128}

执行效果如图:

图9-3