Flutter动画

Posted flutter开发者

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter动画相关的知识,希望对你有一定的参考价值。

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再来看下效果。

Flutter动画【1】

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());
}

Flutter动画【1】

动画状态监听

在前面的例子中我们使用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());
}

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

Flutter动画【1】

CurvedAnimation

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

Flutter动画【1】

先慢后快

Flutter动画【1】

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值。

Flutter动画【1】

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




以上是关于Flutter动画的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段7——CSS动画

错误记录Flutter 混合开发获取 BinaryMessenger 报错 ( FlutterActivityAndFragmentDelegate.getFlutterEngine() )(代码片段

使用嵌套片段和动画对象

在 webview_flutter 中启用捏合和缩放,在哪里添加代码片段 [this.webView.getSettings().setBuiltInZoomControls(true);]

Android:将“ViewPager”动画从片段更改为片段

Android 动画嵌套片段