老孟导读:此篇文章是 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】:

【Flutter 实战】一文学会20多个动画组件的更多相关文章

  1. 【Flutter 实战】17篇动画系列文章带你走进自定义动画

    老孟导读:Flutter 动画系列文章分为三部分:基础原理和核心概念.系统动画组件.8篇自定义动画案例,共17篇. 动画核心概念 在开发App的过程中,自定义动画必不可少,Flutter 中想要自定义 ...

  2. 一文学会JVM性能优化

    实战性能优化 1 重新认知JVM 之前我们画过一张图,是从Class文件到类装载器,再到运行时数据区的过程,现在咱们把这张图不妨丰富完善一下,展示了JVM的大体物理结构图. 执行引擎:用于执行JVM字 ...

  3. [实战]MVC5+EF6+MySql企业网盘实战(23)——文档列表

    写在前面 上篇文章实现了图片列表,这篇文章实现文档列表将轻车熟路,因为逻辑基本相似,只是查询条件的不同.这里将txt,doc,docx,ppt,pptx,xls,xlsx的文件都归为文档列表中. 系列 ...

  4. JavaEE实战——XML文档DOM、SAX、STAX解析方式详解

    原 JavaEE实战--XML文档DOM.SAX.STAX解析方式详解 2016年06月22日 23:10:35 李春春_ 阅读数:3445 标签: DOMSAXSTAXJAXPXML Pull 更多 ...

  5. 【Flutter实战】移动技术发展史

    老孟导读:大家好,这是[Flutter实战]系列文章的第一篇,这并不是一篇Flutter技术文章,而是介绍智能手机操作系统.跨平台技术的演进以及我对各种跨平台技术看法的文章. 智能手机操作系统 塞班( ...

  6. Flutter实战】文本组件及五大案例

    老孟导读:大家好,这是[Flutter实战]系列文章的第二篇,这一篇讲解文本组件,文本组件包括文本展示组件(Text和RichText)和文本输入组件(TextField),基础用法和五个案例助你快速 ...

  7. 【Flutter实战】图片组件及四大案例

    老孟导读:大家好,这是[Flutter实战]系列文章的第三篇,这一篇讲解图片组件,Image有很多高级用法,希望对您有所帮助. 图片组件是Flutter基础组件之一,和文本组件一样必不可少.图片组件包 ...

  8. 【Flutter实战】定位装饰权重组件及柱状图案例

    老孟导读:Flutter中有这么一类组件,用于定位.装饰.控制子组件,比如 Container (定位.装饰).Expanded (扩展).SizedBox (固定尺寸).AspectRatio (宽 ...

  9. 【Flutter实战】自定义滚动条

    老孟导读:[Flutter实战]系列文章地址:http://laomengit.com/guide/introduction/mobile_system.html 默认情况下,Flutter 的滚动组 ...

随机推荐

  1. 第 13 篇:DRF 框架之 API 版本管理

    作者:HelloGitHub-追梦人物 API 不可能一成不变,无论是新增或者删除已有 API,都会对调用它的客户端产生影响.如果对 API 的增删没有管理,随着 API 的增增减减,调用它的客户端就 ...

  2. springboot文件上传 流的方式 后台计算上传进度

    //代码 public static void main(String[] args) throws Exception { String path = "f:/svn/t_dictiona ...

  3. CSMA/CD ,现在的交换式以太网还用吗?谈全双工,半双工与CSMA/CD的关系

    我们知道:以太网访问控制用的是CSMA/CD,即载波侦听多点接入/ 冲突检测,是以广播的方式将数据发送到所有端口: 我们还知道:交换机能主动学习端口所接设备的MAC地址,在获知该端口的MAC 地址后, ...

  4. python中的and与or

    一.问题起源: main=None main=main or sys.modules["__main__"].main main返回的是后面一个值,即 sys.modules[&q ...

  5. stringsream用法

    stringstream: 头文件: #include <sstream> 简单整理一下这玩意的作用,主要有三个吧. 类型转化 字符串拼接 字符串整合(这一个用处特别大!!!!!!!) 先 ...

  6. shell脚本报错:-bash: xxx: /bin/sh^M: bad interpreter: No such file or directory

    今天执行一个shell脚本,然后在执行的时候报错,脚本内容很简单,仅供测试: #!/bin/sh echo "test shell " 具体报错信息如下 [root@localho ...

  7. 五天一体_企业权限管理(SSM整合)

    学于黑马程序员和传智播客联合做的教学项目 感谢 黑马程序员官网 传智播客官网 个人根据教程的每天的工作进度的代码和资料 密码:cti5 b站在线视频 微信搜索"艺术行者",关注并回 ...

  8. Django学习路6_修改数据库为 mysql ,创建mysql及进行迁徙

    在项目的 settings 中修改 DATABASES = { 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os. ...

  9. PHP uksort() 函数

    ------------恢复内容开始------------ 实例 使用用户自定义的比较函数对数组 $arr 中的元素按键名进行排序: <?phpfunction my_sort($a,$b){ ...

  10. PHP constant() 函数

    实例 返回一个常量的值: <?php//define a constantdefine("GREETING","Hello you! How are you tod ...