Flutter开发者

Flutter动画【1】

2018-05-11

Hello 好久不见,我没消失,这不,又给大家更新教程了,笔芯

在前面的文章中我们花了很多的时间去讲了Flutter中的Widget以及用户操作,但是我们却很少去关注与用户的交互效果,当然这并不会导致我们的程序崩溃或者不能实现某个功能,但是它真的会使我们应用程序没有“灵性”,总让人觉得少了些什么,对啊,动画!

说到动画,相比大家对动画都不陌生,当然可能最先接触的应该是flash吧,一般指的是从一种状态到另一种状态的改变,或者说大小、形状、位置的改变。

在Flutter中的动画分为补间(Tween)动画和基于物理(Physics-based)的动画,由于篇幅原因我们今天就先来看下补间动画,当然也是我们接触比较多的动画类型。

补间动画的基本支持类

在Flutter中Animation对象是Flutter动画库中的一个核心类,它生成指导动画的值,没错它仅仅用来生成动画的值,这些值并不会直接没改变界面的展示效果。

Animation对象知道动画的当前状态(例如,它是开始、停止还是向前或向后移动),但它不知道屏幕上显示的内容。

在Flutter中我们使用AnimationController来管理动画,控制动画的开始、结束与快慢。

CurvedAnimation 可以说是动画的插值器,负责控制动画的行为,如是先快再慢还是先快再慢等。

入门补间动画

Animation在Flutter中是一个抽象类,我们并不能直接来是使用它,但是我们可以使用Tween这个子类来使用它。

我们可以使用addListener回调来监听动画值的改变,可以使用addStatusListener回调来监听动画状态的变更

刚刚我们说过,使用Animation并不能直接改变Widget,它只能生成一系列的值,那么到底是不是这样呢?我们还是看个例子

每次我们点击floatingActionButton都会触发动画开始的操作,然后通过监听把当前动画的值打印到控制台上。

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

void main() {
runApp(new MaterialApp(home: new MyApp(),));
}

class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => MyAppState();
}

class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
Animation<double> doubleAnimation;
AnimationController animationController;

@override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("AnimAtion"),),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add), onPressed:onAnimationStart),);
}

@override
void initState() {
super.initState();
animationController = new AnimationController(
vsync: this, duration: const Duration(milliseconds: 2000));
doubleAnimation =
new Tween(begin: 0.0, end: 100.0).animate(animationController)..addListener((){
print(doubleAnimation.value);
});

}

onAnimationStart() {
animationController.forward(from: 0.0);
}

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

上面的代码很简单,我们在Widget初始化时建立了一个AnimationController对象用来控制动画的播放,并设置动画时长为2秒

然后我们建立一个Tween动画,从0.0开始到100.0结束,并且给动画设置监听,动画的值改变时都会触发print方法,把当前的值打印在控制台上。

控制台输出:

I/flutter ( 6616): 0.0

I/flutter ( 6616): 1.38355

I/flutter ( 6616): 2.2180999999999997

I/flutter ( 6616): 3.05225

I/flutter ( 6616): 3.88295

I/flutter ( 6616): 4.7136499999999995

I/flutter ( 6616): ……

I/flutter ( 6616): 99.87325

I/flutter ( 6616): 100.0

好吧,我们还是把动画的值设置给floatcationbar再来看下效果。

1
2
3
4
5

floatingActionButton: new FloatingActionButton(
onPressed: onAnimationStart,
child: Text(numberAnimation.value.toInt().toString()),
)

实现起来也很简单只不过是把floatingActionButton的child变成了一个Text而已。

AnimatedWidget

在上面的例子中我们必须需要通过addListener来获得对动画值变化的监听,但是通过AnimatedWidget我们可以直接获得动画的值并赋值给相应的Widget

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

void main() {
runApp(new MaterialApp(
home: MyApp(),
));
}

class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => MyAppState();
}

class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
Animation<double> numberAnimation;
AnimationController controller;

@override
void initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
numberAnimation = new Tween(begin: 0.0, end: 100.0).animate(controller);
}


onAnimationStart() {
controller.forward(from: 0.0);
}

@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("Animation"),
),
floatingActionButton: new FloatingActionButton(
onPressed: onAnimationStart,
child: AnimationText(animation: numberAnimation,),
),
);
}

dispose() {
controller.dispose();
super.dispose();
}
}

class AnimationText extends AnimatedWidget {
AnimationText({Key key, Animation<double> animation})
: super(key: key, listenable: animation);

@override
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return Text(animation.value.toInt().toString());
}
}

其实使用起来也非常的简单,只不过我们自定义了一个AnimationText继承于AnimatedWidget来获得对动画的监听并给Text赋值,当然程序的运行效果跟上面的例子是一样的。

控制View的大小

在上面的例子中我们通过动画的值来改变了Text显示的值,现在我们来试下改变View的大小。

其实实现起来很简单,只是把动画的值赋值给Widget的宽和高而已(官方例子)

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

class LogoApp extends StatefulWidget {
_LogoAppState createState() => new _LogoAppState();
}

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;

initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
animation = new Tween(begin: 0.0, end: 300.0).animate(controller)
..addListener(() {
setState(() {
// the state that has changed here is the animation object’s value
});
});
controller.forward();
}

Widget build(BuildContext context) {
return new Center(
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: animation.value,
width: animation.value,
child: new FlutterLogo(),
),
);
}

dispose() {
controller.dispose();
super.dispose();
}
}

void main() {
runApp(new LogoApp());
}

动画状态监听

在前面的例子中我们使用ddListener来监听动画值的改变,这里我们使用aaddStatusListener来监听动画状态的变更。

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

class LogoApp extends StatefulWidget {
_LogoAppState createState() => new _LogoAppState();
}

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;

initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
animation = new Tween(begin: 0.0, end: 300.0).animate(controller)
..addListener(() {
setState(() {
// the state that has changed here is the animation object’s value
});
})
..addStatusListener((status){
if(status==AnimationStatus.forward){
print("动画开始");
}else if(status==AnimationStatus.completed){
print("动画结束");
controller.reverse();
}
else if (status == AnimationStatus.dismissed) {
print("动画消失");
controller.forward();
}
});

controller.forward();
}

Widget build(BuildContext context) {
return new Center(
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: animation.value,
width: animation.value,
child: new FlutterLogo(),
),
);
}

dispose() {
controller.dispose();
super.dispose();
}
}

void main() {
runApp(new LogoApp());
}

我们监听动画的状态变更,当动画结束时我们反转动画,当动画的反转也结束后我们从新开始动画,这样动画就会一直这样循环下去。

CurvedAnimation

接下来我们来看下CurvedAnimation,通过CurvedAnimation我们可以实现动画的非线性播放,比如先快后慢

先慢后快

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

/**
* 使用AnimatedBuilder来做动画
*/
class LogoApp extends StatefulWidget {
_LogoAppState createState() => new _LogoAppState();
}
class LogoWidget extends StatelessWidget {
// Leave out the height and width so it fills the animating parent
build(BuildContext context) {
return new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
child: new FlutterLogo(),
);
}
}
class _LogoAppState extends State<LogoApp> with TickerProviderStateMixin {
Animation animation;
AnimationController controller;

initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
final CurvedAnimation curve =
new CurvedAnimation(parent: controller, curve: Curves.bounceInOut);
animation = new Tween(begin: 0.0, end: 300.0).animate(curve);
controller.forward();
}

Widget build(BuildContext context) {
return new GrowTransition(child: new LogoWidget(), animation: animation);
}

dispose() {
controller.dispose();
super.dispose();
}
}

void main() {
runApp(new LogoApp());
}

class GrowTransition extends StatelessWidget {
GrowTransition({this.child, this.animation});

final Widget child;
final Animation<double> animation;

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

实现起来也很简单,我们只需要把CurvedAnimation传递给Tween即可,另外这个例子中我们使用AnimatedBuilder来构建动画Widget,其实跟前面的AnimatedWdiget是类似的。

Curves类中有很多内置的非线性动画效果,大家可以在下面自己试下,当然 大家也可以根据自己的需要定制属于自己的非线性动画效果

动画的并行运行

当然我们有时候需要多个动画同时作用的效果,比如大小的改变和透明度的改变

每一个Tween管理动画的一种效果。例如:

1
2
3
4
5
6
final AnimationController controller =
new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
final Animation<double> sizeAnimation =
new Tween(begin: 0.0, end: 300.0).animate(controller);
final Animation<double> opacityAnimation =
new Tween(begin: 0.1, end: 1.0).animate(controller);
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
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';

class AnimatedLogo extends AnimatedWidget {
// The Tweens are static because they don't change.
static final _opacityTween = new Tween<double>(begin: 0.1, end: 1.0);
static final _sizeTween = new Tween<double>(begin: 0.0, end: 300.0);

AnimatedLogo({Key key, Animation<double> animation})
: super(key: key, listenable: animation);

Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return new Center(
child: new Opacity(
opacity: _opacityTween.evaluate(animation),
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: _sizeTween.evaluate(animation),
width: _sizeTween.evaluate(animation),
child: new FlutterLogo(),
),
),
);
}
}

class LogoApp extends StatefulWidget {
_LogoAppState createState() => new _LogoAppState();
}

class _LogoAppState extends State<LogoApp> with TickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;

initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
animation = new CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn);
controller.forward();
}

Widget build(BuildContext context) {
return new AnimatedLogo(animation: animation);
}

dispose() {
controller.dispose();
super.dispose();
}
}

void main() {
runApp(new LogoApp());
}

你可以通过sizeAnimation.value来获取大小,通过opacityAnimation.value来获取不透明度,但AnimatedWidget的构造函数只接受一个动画对象。 为了解决这个问题,该示例创建了自己的Tween对象并显式计算了这些值。

其build方法.evaluate()在父级的动画对象上调用Tween函数以计算所需的size和opacity值。

好吧,今天先写这么多吧,大家在下面要多多练习哈。

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

打赏请备注姓名或者昵称,方便我后期统计哦

关注公众号,及时查阅最新文章