【Flutter 实战】一文学会20多个动画组件

老孟导读:此篇文章是 Flutter 动画系列文章第三篇,后续还有动画序列、过度动画、转场动画、自定义动画等。

Flutter 系统提供了20多个动画组件,只要你把前面【动画核心】(文末有链接)的文章看明白了,这些组件对你来说是非常轻松的,这些组件大部分都是对常用操作的封装。

显示动画组件

回顾上一篇【动画核心】的文章中创建动画三个必须的步骤:

  1. 创建 AnimationController。
  2. 监听 AnimationController,调用 setState 刷新UI。
  3. 释放 AnimationController。

看第二步,每个动画都需要这个步骤,因此对其封装,命名为 MyAnimatedWidget:

class MyAnimatedWidget extends StatefulWidget {
  final AnimationController controller;
  final Widget child;

  const MyAnimatedWidget(
      {Key key, @required this.controller, @required  this.child})
      : super(key: key);

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

class _MyAnimatedWidgetState extends State<MyAnimatedWidget> {
  @override
  void initState() {
    super.initState();
    widget.controller.addListener(() {
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
    
  @override
  void dispose() {
    super.dispose();
    widget.controller.dispose();
  }
}

自定义的动画组件只有两个功能:

  1. 监听 AnimationController,调用 setState
  2. 释放 AnimationController。

而 AnimationController 的创建需要开发者自行创建,为什么封装在自定义组件内?这个后面会介绍。

其实这个组件不用我们自己封装,因为系统已经封装好了,在学习 Flutter 的过程中自定义组件是非常重要的,因此多封装一些组件,即使是系统已经存在的,用自己和系统的进行对比,可以极大的提高我们自定义组件的能力。

系统封装的类似上面的组件是 AnimatedWidget,此类是抽象类,源代码:

区别:

  1. 我们使用 监听 AnimationController,调用 setState ,而系统使用 Listenable,Listenable 是一个维护侦听器列表的对象,用于通知客户端该对象已被更新。

    Listenable 有两个变体:

    1. ValueListenable :扩展[Listenable]接口的接口,具有当前值的概念。
    2. Animation:一个扩展[ValueListenable]接口的接口,添加方向(正向或反向)的概念。

    AnimationController 的继承结构:

    AnimationController 也是继承自 Listenable,因此使用 Listenable 适用的范围更广,不仅仅可以用于 Animation ,还可以用于 ChangeNotifier。

  2. 由于使用了 Listenable,因此监听和释放使用listenable.addListenerlistenable.removeListener

AnimatedWidget 是一个抽象类,不能直接使用,其子类包括:

ScaleTransition 为例使用方式:

class AnimationDemo extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _AnimationDemo();
}

class _AnimationDemo extends State<AnimationDemo>
    with SingleTickerProviderStateMixin {
  AnimationController _animationController;
  Animation _animation;

  @override
  void initState() {
    _animationController =
        AnimationController(duration: Duration(seconds: 2), vsync: this);

    _animation = Tween(begin: .5, end: .1).animate(_animationController);

    //开始动画
    _animationController.forward();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return ScaleTransition(
      scale: _animation,
      child: Container(
        height: 200,
        width: 200,
        color: Colors.red,
      ),
    );
  }

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

和【动画核心】中写法唯一的不同是不需要主动调用 setState

AnimatedWidget 其他子类的用法类似,不在一一介绍,其他组件的详细用法可到 http://laomengit.com/flutter/widgets/widgets_structure.html 中查看。

隐式动画组件

AnimatedWidget 只是封装了 setState,系统是否有封装 AnimationController、Tween、Curve且自动管理AnimationController的组件呢?有的,此组件就是 ImplicitlyAnimatedWidget,ImplicitlyAnimatedWidget 也是一个抽象类,其子类包括:

AnimatedOpacity 为例使用方式:

class AnimatedWidgetDemo extends StatefulWidget {
  @override
  _AnimatedWidgetDemoState createState() => _AnimatedWidgetDemoState();
}

class _AnimatedWidgetDemoState extends State<AnimatedWidgetDemo> {
  double _opacity = 1.0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedOpacity(
        opacity: _opacity,
        duration: Duration(seconds: 2),
        child: GestureDetector(
          onTap: () {
            setState(() {
              _opacity = 0;
            });
          },
          child: Container(
            height: 60,
            width: 150,
            color: Colors.blue,
          ),
        ),
      ),
    );
  }
}

使用 AnimatedOpacity 我们并没有主动创建 AnimationController 和 Tween,是因为 AnimatedOpacity 内部已经创建了。

所以别看 Flutter 内置了20多种动画组件,90% 都是对上面两种方式的封装,分别称为隐式动画组件 和 显示动画组件:

  • 隐式动画组件:只需提供给组件动画开始、结束值,组件创建 AnimationController、Curve、Tween,执行动画,释放AnimationController,我们称之为隐式动画组件,隐式动画组件有: AnimatedAlignAnimatedContainerAnimatedDefaultTextStyleAnimatedOpacityAnimatedPaddingAnimatedPhysicalModelAnimatedPositionedAnimatedPositionedDirectionalAnimatedThemeSliverAnimatedOpacityTweenAnimationBuilderAnimatedContainer 等。
  • 显示动画组件:需要设置 AnimationController,控制动画的执行,使用显式动画可以完成任何隐式动画的效果,甚至功能更丰富一些,不过你需要管理该动画的 AnimationController 生命周期,AnimationController 并不是一个控件,所以需要将其放在 stateful 控件中。显示动画组件有:AlignTransitionAnimatedBuilderAnimatedModalBarrierDecoratedBoxTransitionDefaultTextStyleTransitionPositionedTransitionRelativePositionedTransitionRotationTransitionScaleTransitionSizeTransitionSlideTransitionFadeTransition 等。

不难看出,使用隐式动画控件,代码更简单,而且无需管理 AnimationController 的生命周期,有人觉得隐式动画组件多方便啊,为什么还要显示动画组件呢?因为:封装的越复杂,使用越简单,往往伴随着功能越不丰富。比如想让动画一直重复执行,隐式动画组件是无法实现的。

显示动画组件和隐式动画组件中各有一个万能的组件,它们是 AnimatedBuilderTweenAnimationBuilder,当系统中不存在我们想要的动画组件时,可以使用这两个组件,以 AnimatedBuilder 为例,实现 Container 大小和颜色同时动画,

class AnimatedBuilderDemo extends StatefulWidget {
  @override
  _AnimatedBuilderDemoState createState() => _AnimatedBuilderDemoState();
}

class _AnimatedBuilderDemoState extends State<AnimatedBuilderDemo>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<Color> _colorAnimation;
  Animation<Size> _sizeAnimation;

  @override
  void initState() {
    _controller =
        AnimationController(vsync: this, duration: Duration(seconds: 2));

    _colorAnimation =
        ColorTween(begin: Colors.blue, end: Colors.red).animate(_controller);
    _sizeAnimation =
        SizeTween(begin: Size(100.0, 50.0), end: Size(200.0, 100.0))
            .animate(_controller);

    _controller.forward();
    super.initState();
  }

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

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

AnimatedBuilderTweenAnimationBuilder 本质上和其他动画组件没有区别,只是给了我们更高的灵活性。

如何选取

Flutter 内置的动画组件分为两种:隐式动画组件显示动画组件 ,显示动画组件只封装了 setState 方法,需要开发者创建 AnimationController,并管理 AnimationController。隐式动画组件封装了 AnimationController、Curve、Tween,只需提供给组件动画开始、结束值,其余由系统管理。

隐式动画组件可以完成效果,显示动画组件都可以完成,那么什么时候使用隐式动画组件?什么时候使用显示动画组件?

  1. 判断你的动画组件是否一直重复,比如一直转圈的loading动画,如果是选择显式动画。
  2. 判断你的动画组件是否需要多个组件联动,如果是选择显式动画。
  3. 判断你的动画组件是否需要组合动画,如果是选择显式动画。
  4. 如果上面三个条件都是否,就选择隐式动画组件,判断是否已经内置动画组件,如果没有,使用 TweenAnimationBuilder,有就直接使用内置动画组件。
  5. 选择显式动画组件,判断是否已经内置动画组件,如果没有,使用 AnimatedBuilder,有就直接使用内置动画组件。

逻辑图如下:

还有一个简单的区分办法:如果你的动画相对比较简单,动画从一种状态过渡到另一种状态,不需要单独控制 AnimationController,这种情况下,隐式动画组件一般可以就可以实现。

不过也没有必要特别纠结使用隐式动画组件还是显示动画组件,不管使用哪一种,实现效果即可。

交流

老孟Flutter博客地址(330个控件用法):http://laomengit.com

欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335