gavin's home.

Flutter之Animation

Word count: 1.7kReading time: 7 min
2020/01/19 Share

在用户体验当中,设计巧妙的动画可以让UI体验更加的直观,而Flutter作为一个友好的跨平台框架对此作了很好的支持。接下来我将把我在这段时间学习的Flutter动画知识分享给大家,希望大家在学完之后对此可以有一个初步的了解。

基本概念

首先,我简单表述下Flutter中动画的基本概念。动画中最核心的类是Animation<T>类。而这个类最基本的功能就是在一段时间里不停的“吐出”一定的值,而这个值可以通过对象的value属性获取的。而这个value的类型就是T,然后做动画的的widget根据获取到的value值不断地rubuild。这就是动画的基本原理。Animation<T>这个类的部分子类是有状态的,而所谓的状态,我的个人理解就是它自身是可以被监听的,那么该如何创建一个简单的动画呢?以下代码就是简单创建一个动画的例子:

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
import 'package:flutter/material.dart';

void main() => runApp(Animation1());

class Animation1 extends StatefulWidget{
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _animation1State();
}

}

class _animation1State extends State<Animation1> with SingleTickerProviderStateMixin{
AnimationController controller;
CurvedAnimation curve;
Animation<double> animation;

@override
void initState() {
super.initState();
controller = AnimationController(vsync: this,duration: Duration(seconds: 2));
curve = CurvedAnimation(parent: controller,curve: Curves.bounceInOut);
animation = Tween<double>(begin: 10,end: 200).animate(curve)
..addListener((){
setState(() {
});
})
..addStatusListener((state){
if (state == AnimationStatus.completed) {
controller.reverse();
}else if (state == AnimationStatus.dismissed){
controller.forward();
}
});
controller.forward();

}

@override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
title: 'AnimateApp',
theme: ThemeData(
primaryColor: Colors.redAccent
),
home: Scaffold(
appBar: AppBar(
title: Text('AnimateApp'),
),
body: Center(
child: Container(
width: animation.value,
height: animation.value,
decoration: BoxDecoration(
color: Colors.blue
),
),
),
),
)
}

@override
void dispose() {
// TODO: implement dispose
controller.dispose();
super.dispose();
}

上面提到了四个关键的类:AnimationController,CurvedAnimation,Tween<double>以及Animation<double>。接下来我们分别介绍这四个类。

  • AnimationController

这个继承是Animation类.是一个特殊的Animation对象,每当GPU重新绘制屏幕,准备新的帧的时候,他就会生成一个新值,而这个值的区间就是0.0到1.0,生成的速度默认情况是线性的。除了作为动画本身,她也可以控制动画,观察它的构造函数可以看出这个动画持续的时间是2秒,至于前面的vsync是为了在动画过程不绘制屏幕外的动画,只绘制屏幕以内可视区间的动画来保证资源的有效利用。

  • CurvedAnimation

这个类也是继承Animation类,看代码中的构造函数可以看出,这个类接受Animation<double>(父级)以及Curve类为输入,并使用父级的值作为输入提供给曲线来确定输出,这个类是无状态的不可变的,只是将父级的输入转化了一遍。

  • Tween<double>

这个类是继承Animatable的,而这个类的作用其实就是重新定义了begin和end的值的类型和范围,并通过animate()函数来返回一个新Animation子类。

  • Animation<double>

这个类是我们大部分情况所用到的,它自身是有状态的可以被监听的,默认的范围为0.0到1.0。在代码中通过Tween<double>这个类的映射改变了区间值,使其变成10到200的范围区间。然后通过addListener方法来不断的调用setState()方法rebuild widget达到动画的效果。下图简单显示了代码中的动画流程。

AnimatedWidget

上面我们讲完了一个基本动画该如何创建,而在Animation类和Widget(要做动画的)之间最重要的桥接就是addListener()以及setState()方法,接下来我们将讲述另一种创建动画的方式而不需要显式的调用这两个方法。
代码如下:

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
import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';

void main() => runApp(animation2());

class animation2 extends StatefulWidget {
animation2({Key key}) : super(key: key);

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

class _animation2State extends State<animation2> with SingleTickerProviderStateMixin {

AnimationController controller;
Animation<double> animation;

@override
void initState() {
// TODO: implement initState
super.initState();
controller = AnimationController(vsync: this,duration: Duration(seconds: 2));
final CurvedAnimation curve = CurvedAnimation(parent: controller,curve: Curves.bounceInOut);
animation = Tween<double>(begin: 0,end: 300).animate(curve);
controller.forward();

}

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AnimationApp',
theme: ThemeData(
primaryColor: Colors.red
),
home: Scaffold(
appBar: AppBar(
title: Text('AnimateApp'),
backgroundColor: Colors.brown,
),
body: Center(
child: Animation2Square(animation: animation),
),
)
);
}
}

class Animation2Square extends AnimatedWidget{

Animation2Square({Key key, Animation<double> animation})
:super(key:key,listenable:animation);
@override
Widget build(BuildContext context) {
final Animation<double> animation = listenable as Animation<double>;
// TODO: implement build
return Container(
margin: EdgeInsets.symmetric(vertical: 10),
width: animation.value,
height: animation.value,
decoration: BoxDecoration(
color: Colors.blue
),
);
}

}

Animation2Square继承自AnimatedWidget, 不需要保持State对象来hold动画,而是接受一个Listenable类型的参数,而Animation类本身就继承自这个类。可以不断的rebuild widget来执行动画。
但是以上的代码有一个问题就是改变动画的时候需要改变渲染方块的widget。所以我们这里可以把任务分到不同的类中:

  • 渲染方块
  • 定义动画对象
  • 渲染过渡效果

在这里我们就用到了AnimatedBuilder类。

AnimatedBuilder

话不多说,先上代码:

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
86
87
88
89
90
91
92
import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';


void main() => runApp(animation3());

class animation3 extends StatefulWidget {
animation3({Key key}) : super(key: key);

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

class _animation3State extends State<animation3> with SingleTickerProviderStateMixin{

AnimationController controller;
Animation<double> animation;

@override
void initState() {
// TODO: implement initState
super.initState();
controller = AnimationController(vsync: this,duration: Duration(seconds: 4));
animation = Tween<double>(begin: 20,end: 300).animate(controller)
..addStatusListener((state){
if (state == AnimationStatus.completed) {
controller.reverse();
}else if (state == AnimationStatus.dismissed){
controller.forward();
}

});

controller.forward();
}

@override
Widget build(BuildContext context) {
return MaterialApp(
title: "AnimationApp",
theme: ThemeData(
primaryColor: Colors.red,
),
home: Scaffold(
appBar: AppBar(
title: Text('AnimationApp'),
backgroundColor: Colors.brown,
),
body: Center(
child: GrowTranstion(animation: animation,animationWidget: animation3Squal()),
),
),
);
}
}


class GrowTranstion extends StatelessWidget {
GrowTranstion({this.animation,this.animationWidget});

final Widget animationWidget;
final Animation<double> animation;

@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: animation,
child: animationWidget,
builder: (BuildContext context, Widget child) {
return Container(
width: animation.value,
height: animation.value,
child: animationWidget,
);
},

),
);
}
}

class animation3Squal extends StatelessWidget {

Widget build(BuildContext context) => Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: Container(),
decoration: BoxDecoration(
color: Colors.blue
),
);
}

在上述代码中,我们将动画本身以及做动画的widget分开。新建了一个GrowTranstion对象来桥接这两个对象,然后在build方法中返回AnimatedBuilder来渲染以达到动画的效果。

总结

  • Flutter中的动画的核心类是Animation。而一个动画的变现的形式就是在它的生命周期内发生变化的特定类型的值。
  • 大多数的需要执行动画的widgets都需要接受一个Animation对象作为参数。从而能够获取动画当前的值以及应该监听哪些值的修改。
CATALOG
  1. 1. 基本概念
  2. 2. AnimatedWidget
  3. 3. AnimatedBuilder
  4. 4. 总结