Flutter和Dart系列十二:动画(Animation)

释放双眼,带上耳机,听听看~!

一个App中如果能有优秀的动画效果,能让App看起来显得更加高大上。此篇我们就来介绍一下Flutter中Animation体系。

我们先来一个简单的例子,来实现透明度渐变动画:


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
1class FadeInDemo extends StatefulWidget {
2  @override
3  State createState() {
4    return _FadeInDemoState();
5  }
6}
7
8class _FadeInDemoState extends State {
9  double opacity = 0.0;
10
11  @override
12  Widget build(BuildContext context) {
13    return Column(
14      children: <Widget>[
15        MaterialButton(
16          child: Text(
17            "Click Me",
18            style: TextStyle(color: Colors.blueAccent),
19          ),
20          onPressed: () => setState(() {
21            opacity = 1;
22          }),
23        ),
24        AnimatedOpacity(
25            duration: const Duration(seconds: 2),
26            opacity: opacity,
27            child: Text('Flutter Animation Demo01')
28            )
29      ],
30    );
31  }
32}
33

这里我们借助于AnimatedOpacity来实现渐变:我们通过点击按钮来触发动画效果,如下图所示。

Flutter和Dart系列十二:动画(Animation)

我们来总结一下实现步骤:

  • 实现AnimatedOpacity来包裹需要实现透明度渐变动画的Widget,并指定duration和opacity参数。这俩参数也好理解:duration自然是动画时间,opacity表示透明度(取值范围为0~1,0表示透明)
    • 触发动画:通过setState()方法,我们可以直接指定opacity的最终值(为1,即完全显示)。因为所谓的动画,肯定是有起始状态和结束状态,然后在指定的动画时间内慢慢发生变化。

2.使用AnimatedContainer来实现其他属性变化的动画.


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
1    class AnimatedContainerDemo extends StatefulWidget {
2      @override
3      State createState() {
4        return _AnimatedContainerDemoState();
5      }
6    }
7
8    class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
9      Color color;
10      double borderRadius;
11      double margin;
12
13      double randomBorderRadius() {
14        return Random().nextDouble() * 64;
15      }
16
17      double randomMargin() {
18        return Random().nextDouble() * 64;
19      }
20
21      Color randomColor() {
22        return Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
23      }
24
25      void change() {
26        setState(() {
27          initState();
28        });
29      }
30
31      @override
32      void initState() {
33        color = randomColor();
34        borderRadius = randomBorderRadius();
35        margin = randomMargin();
36      }
37
38      @override
39      Widget build(BuildContext context) {
40        return Scaffold(
41          body: Center(
42            child: Column(
43              children: <Widget>[
44                SizedBox(
45                  width: 128,
46                  height: 128,
47                  child: AnimatedContainer(
48                    curve: Curves.easeInOutBack,
49                    duration: const Duration(milliseconds: 400),
50                    margin: EdgeInsets.all(margin),
51                    decoration: BoxDecoration(
52                      color: color,
53                      borderRadius: BorderRadius.circular(borderRadius),
54                    ),
55                  ),
56                ),
57                MaterialButton(
58                  color: Theme.of(context).primaryColor,
59                  child: Text(
60                    'change',
61                    style: TextStyle(color: Colors.white),
62                  ),
63                  onPressed: () => change(),
64                )
65              ],
66            ),
67          ),
68        );
69      }
70    }
71

运行效果如下:

Flutter和Dart系列十二:动画(Animation)

我们这次同时修改了margin、color以及borderRadius三个属性。AnimatedContainer的使用思路和AnimatedOpacity类似:

  • 包裹子widget,指定duration及相关属性参数
    • 在setState方法中指定属性的动画终止状态

实际上我们刚刚介绍的两种实现方式被称之为隐式动画(implicit animation),可以理解成对于Animation子系统进行了一层封装,方便我们开发者使用。下面我们正式来介绍Animation子系统的重要组成类:

3.Animation类:通过这个类,我们可以知道当前的动画值(animation.value)以及当前的状态(通过设置对应的监听listener),但是对于屏幕上如何显示、widget如何渲染,它是不知道的,换句话说也不是它所关心的,这点从软件设计上耦合性也更低。其次,从代码角度上,它是一个抽象类:

Flutter和Dart系列十二:动画(Animation)

4.AnimationController类。

Flutter和Dart系列十二:动画(Animation)

从类本身上看,它是继承自Animation类的,并且泛型类型为double。从作用上来看,我们可以通过AnimationController来指定动画时长,以及它提供的forward()、reverse()方法来触发动画的执行。

5.Curved­Animation类:同样继承自Animation类,并且泛型类型为double。它主要用来描述非线性变化的动画,有点类似Android中的属性动画的插值器。

Flutter和Dart系列十二:动画(Animation)

6.Tween类.

Flutter和Dart系列十二:动画(Animation)

从类的层次结构上,它有所不同,不再是继承自Animation,而是继承自Animatable。它主要用来指定起始状态和终止状态的。

好,我们已经对这四个类有了一定的了解,下面我们就从实例来看看他们是如何结合在一起使用的。

7.实例一:


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
1class LogoDemo extends StatefulWidget {
2  @override
3  State createState() => _LogoState();
4}
5
6class _LogoState extends State with SingleTickerProviderStateMixin {
7
8 // 注释1:这里已经出现了我们前面提到的Animation和AnimationController类
9  Animation<double> animation;
10  AnimationController controller;
11
12  @override
13  void initState() {
14    super.initState();
15    // 注释2:
16    // 在构造一个AnimationController对象时,我们需要传递两个参数
17    // vsync:主要为了防止一些超出屏幕之外的动画而导致的不必要的资源消耗。我们这里就传递
18    //        this,除此之外,我们还需要使用with关键字来接入SingleTickerProviderStateMixin类型
19    // duration:指定动画时长
20    controller =
21        AnimationController(vsync: this, duration: const Duration(seconds: 2));
22
23    // 注释3: 通过Tween对象来指定起始值和终止值,并且通过animate方法返回一个Animation对象,
24    //             并且设置了监听,最后在监听回调中调用setState(),从而刷新页面
25    animation = Tween<double>(begin: 0, end: 300).animate(controller)
26      ..addListener(() {
27        setState(() {});
28      });
29
30     // 注释4: 启动动画
31    controller.forward();
32  }
33
34  @override
35  Widget build(BuildContext context) {
36    return Center(
37      child: Container(
38        margin: EdgeInsets.symmetric(vertical: 10),
39        // 注释5:通过Animation对象类获取对应的变化值
40        height: animation.value,
41        width: animation.value,
42        child: FlutterLogo(),
43      ),
44    );
45  }
46
47  @override
48  void dispose() {
49  // 注释6:对controller解注册
50    controller.dispose();
51    super.dispose();
52  }
53}
54

运行效果如图所示:

Flutter和Dart系列十二:动画(Animation)

这里涉及到了两个Dart语言本身的知识点:mixin和..。mixin这里推荐一篇medium上的文章:https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3;而..很简单,它就是为了方便链式调用。我们可以看下前面addListener()方法,方法本身返回的void类型,但是我们最终却返回了一个Animation类型的对象。这其中就是..起到的作用,它可以使得方法返回调用这个方法的对象本身。

8.使用AnimatedWidget来重构上面的代码:


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
1class LogoDemo extends StatefulWidget {
2  @override
3  State createState() => _LogoState();
4}
5
6class _LogoState extends State with SingleTickerProviderStateMixin {
7  Animation<double> animation;
8  AnimationController controller;
9
10  @override
11  void initState() {
12    super.initState();
13    controller =
14        AnimationController(vsync: this, duration: const Duration(seconds: 2));
15    animation = Tween<double>(begin: 0, end: 300).animate(controller)
16      ..addStatusListener((status) {
17        if (status == AnimationStatus.completed) {
18          controller.reverse();
19        } else if (status == AnimationStatus.dismissed) {
20          controller.forward();
21        }
22
23        print('$status');
24      });
25
26    controller.forward();
27  }
28
29  @override
30  Widget build(BuildContext context) {
31    return AnimatedLogo(animation: animation);
32  }
33
34  @override
35  void dispose() {
36    controller.dispose();
37    super.dispose();
38  }
39}
40
41
42class AnimatedLogo extends AnimatedWidget {
43  AnimatedLogo({Key key, Animation<double> animation})
44      : super(key: key, listenable: animation);
45
46  @override
47  Widget build(BuildContext context) {
48    final animation = listenable as Animation;
49    return Center(
50      child: Container(
51        margin: EdgeInsets.symmetric(vertical: 10),
52        height: animation.value,
53        width: animation.value,
54        child: FlutterLogo(),
55      ),
56    );
57  }
58}
59

先看效果:

Flutter和Dart系列十二:动画(Animation)

大部分代码是和之前的例子是一样的,不同的是:

  • 使用AnimatedWidget,并且Animation对象作为参数传递进来
  • 省略了在addListener的回调里调用setState方法来触发页面刷新

这样写的好处:

  • 省去了需要调用setState的重复代码
  • 使得程序耦合性更低。试想一下,我们的App中有多处都需要实现Logo的resize动画,这个时候我们只需要在使用处定义Animation的描述,最后都使用这里的AnimatedLogo。这样做就使得Widget和Animation的描述进行分离。

我们可以去看一下AnimatedWidget类的源码:


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
1abstract class AnimatedWidget extends StatefulWidget {
2  /// Creates a widget that rebuilds when the given listenable changes.
3  ///
4  /// The [listenable] argument is required.
5  const AnimatedWidget({
6    Key key,
7    @required this.listenable,
8  }) : assert(listenable != null),
9       super(key: key);
10
11  /// The [Listenable] to which this widget is listening.
12  ///
13  /// Commonly an [Animation] or a [ChangeNotifier].
14  final Listenable listenable;
15
16  /// Override this method to build widgets that depend on the state of the
17  /// listenable (e.g., the current value of the animation).
18  @protected
19  Widget build(BuildContext context);
20
21  /// Subclasses typically do not override this method.
22  @override
23  _AnimatedState createState() => _AnimatedState();
24
25  @override
26  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
27    super.debugFillProperties(properties);
28    properties.add(DiagnosticsProperty<Listenable>('animation', listenable));
29  }
30}
31
32class _AnimatedState extends State<AnimatedWidget> {
33  @override
34  void initState() {
35    super.initState();
36    widget.listenable.addListener(_handleChange);
37  }
38
39  @override
40  void didUpdateWidget(AnimatedWidget oldWidget) {
41    super.didUpdateWidget(oldWidget);
42    if (widget.listenable != oldWidget.listenable) {
43      oldWidget.listenable.removeListener(_handleChange);
44      widget.listenable.addListener(_handleChange);
45    }
46  }
47
48  @override
49  void dispose() {
50    widget.listenable.removeListener(_handleChange);
51    super.dispose();
52  }
53
54  void _handleChange() {
55    setState(() {
56      // The listenable's state is our build state, and it changed already.
57    });
58  }
59
60  @override
61  Widget build(BuildContext context) => widget.build(context);
62}
63

可以看到在initState方法里,添加了动画监听,回调的执行逻辑为_handleChange()方法,而_handleChange()的实现就是调用了setState方法,这点和我们之前在第7条中例子的写法一样。也就是说,AnimatedWidget只是做了一层封装而已。

9.使用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
1class LogoDemo extends StatefulWidget {
2  @override
3  State createState() => _LogoState();
4}
5
6class _LogoState extends State with SingleTickerProviderStateMixin {
7  Animation<double> animation;
8  AnimationController controller;
9
10  @override
11  void initState() {
12    super.initState();
13    controller =
14        AnimationController(vsync: this, duration: const Duration(seconds: 2));
15    animation = Tween<double>(begin: 0, end: 300).animate(controller);
16    controller.forward();
17  }
18
19  @override
20  Widget build(BuildContext context) {
21    return GrowTransition(child: LogoWidget(), animation: animation);
22  }
23
24  @override
25  void dispose() {
26    controller.dispose();
27    super.dispose();
28  }
29}
30
31
32class LogoWidget extends StatelessWidget {
33  @override
34  Widget build(BuildContext context) {
35    return Container(
36      margin: EdgeInsets.symmetric(vertical: 10),
37      child: FlutterLogo(),
38    );
39  }
40}
41
42class GrowTransition extends StatelessWidget {
43  GrowTransition({this.child, this.animation});
44
45  final Widget child;
46  final Animation<double> animation;
47
48  @override
49  Widget build(BuildContext context) {
50    return Center(
51      child: AnimatedBuilder(
52          animation: animation,
53          builder: (context, child) => Container(
54            height: animation.value,
55            width: animation.value,
56            child: child,
57          ),
58          child: child),
59    );
60  }
61}
62

我们可以在任意地方使用这里GrowTransition,代码进行进一步分离。

10.使用Transition。

Flutter还为我们提供一些封装好的Transition,方便我们实现动画效果,下面我们就以ScaleTransition为例,说明如何去使用这些Transition。


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
1class ScaleTransitionDemo extends StatefulWidget {
2  @override
3  State createState() => _LogoState();
4}
5
6class _LogoState extends State with SingleTickerProviderStateMixin {
7  Animation<double> animation;
8  AnimationController controller;
9
10  @override
11  void initState() {
12    super.initState();
13
14    controller =
15        AnimationController(vsync: this, duration: const Duration(seconds: 2));
16    animation = Tween<double>(begin: 0, end: 10).animate(controller);
17    controller.forward();
18  }
19
20  @override
21  Widget build(BuildContext context) {
22    return Center(
23        child: ScaleTransition(
24      scale: animation,
25      child: FlutterLogo(),
26    ));
27  }
28
29  @override
30  void dispose() {
31    controller.dispose();
32    super.dispose();
33  }
34}
35

Flutter和Dart系列十二:动画(Animation)

ScaleTransition在使用时需要指定两个参数:

  • scale: 就是一个Animation对象
  • child: 需要实现缩放动画的widget

最后再注意一下Tween中指定的值所表示的含义,它表示的倍数。比如我们这里end填入了10,表示动画结束状态为放大10倍。我们可以通过ScaleTransition的源码来说服大家:


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
1class ScaleTransition extends AnimatedWidget {
2  /// Creates a scale transition.
3  ///
4  /// The [scale] argument must not be null. The [alignment] argument defaults
5  /// to [Alignment.center].
6  const ScaleTransition({
7    Key key,
8    @required Animation<double> scale,
9    this.alignment = Alignment.center,
10    this.child,
11  }) : assert(scale != null),
12       super(key: key, listenable: scale);
13
14  /// The animation that controls the scale of the child.
15  ///
16  /// If the current value of the scale animation is v, the child will be
17  /// painted v times its normal size.
18  Animation<double> get scale => listenable;
19
20  /// The alignment of the origin of the coordinate system in which the scale
21  /// takes place, relative to the size of the box.
22  ///
23  /// For example, to set the origin of the scale to bottom middle, you can use
24  /// an alignment of (0.0, 1.0).
25  final Alignment alignment;
26
27  /// The widget below this widget in the tree.
28  ///
29  /// {@macro flutter.widgets.child}
30  final Widget child;
31
32  @override
33  Widget build(BuildContext context) {
34    final double scaleValue = scale.value;
35    final Matrix4 transform = Matrix4.identity()
36      ..scale(scaleValue, scaleValue, 1.0);
37    return Transform(
38      transform: transform,
39      alignment: alignment,
40      child: child,
41    );
42  }
43}
44

可以看到,ScaleTransition也是继承自我们前面已经介绍过的AnimatedWidget,然后重点关注build()方法里,用到了Matrix4矩阵,这里的scale.value实际上就是Animation.value,而Matrix4.identity()..scale(),它的三个参数分别表示在x轴、y轴以及z轴上缩放的倍数。

与ScaleTransition类似的还有SlideTransition、RotationTransition等等,读者可以自己去尝试一下,这里就不在一一赘述了。

给TA打赏
共{{data.count}}人
人已打赏
安全技术

C++回调函数

2022-1-11 12:36:11

安全运维

Nginx防盗链

2021-8-18 16:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索