【Flutter 实战】一文学会20多个动画组件
老孟导读:此篇文章是 Flutter 动画系列文章第三篇,后续还有动画序列、过度动画、转场动画、自定义动画等。
Flutter 系统提供了20多个动画组件,只要你把前面【动画核心】(文末有链接)的文章看明白了,这些组件对你来说是非常轻松的,这些组件大部分都是对常用操作的封装。
显示动画组件
回顾上一篇【动画核心】的文章中创建动画三个必须的步骤:
- 创建 AnimationController。
- 监听 AnimationController,调用
setState
刷新UI。 - 释放 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();
}
}
自定义的动画组件只有两个功能:
- 监听 AnimationController,调用
setState
。 - 释放 AnimationController。
而 AnimationController 的创建需要开发者自行创建,为什么封装在自定义组件内?这个后面会介绍。
其实这个组件不用我们自己封装,因为系统已经封装好了,在学习 Flutter 的过程中自定义组件是非常重要的,因此多封装一些组件,即使是系统已经存在的,用自己和系统的进行对比,可以极大的提高我们自定义组件的能力。
系统封装的类似上面的组件是 AnimatedWidget,此类是抽象类,源代码:
区别:
- 我们使用 监听 AnimationController,调用
setState
,而系统使用 Listenable,Listenable 是一个维护侦听器列表的对象,用于通知客户端该对象已被更新。 Listenable 有两个变体: AnimationController 的继承结构:
AnimationController 也是继承自 Listenable,因此使用 Listenable 适用的范围更广,不仅仅可以用于 Animation ,还可以用于 ChangeNotifier。
- ValueListenable :扩展[Listenable]接口的接口,具有当前值的概念。
- Animation:一个扩展[ValueListenable]接口的接口,添加方向(正向或反向)的概念。
- 由于使用了 Listenable,因此监听和释放使用
listenable.addListener
和listenable.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,我们称之为隐式动画组件,隐式动画组件有:AnimatedAlign、AnimatedContainer、AnimatedDefaultTextStyle、AnimatedOpacity、AnimatedPadding、AnimatedPhysicalModel、AnimatedPositioned、AnimatedPositionedDirectional、AnimatedTheme、SliverAnimatedOpacity、TweenAnimationBuilder、AnimatedContainer 等。
- 显示动画组件:需要设置 AnimationController,控制动画的执行,使用显式动画可以完成任何隐式动画的效果,甚至功能更丰富一些,不过你需要管理该动画的 AnimationController 生命周期,AnimationController 并不是一个控件,所以需要将其放在 stateful 控件中。显示动画组件有:AlignTransition、AnimatedBuilder、AnimatedModalBarrier、DecoratedBoxTransition、DefaultTextStyleTransition、PositionedTransition、RelativePositionedTransition、RotationTransition、ScaleTransition、SizeTransition、SlideTransition 、FadeTransition 等。
不难看出,使用隐式动画控件,代码更简单,而且无需管理 AnimationController 的生命周期,有人觉得隐式动画组件多方便啊,为什么还要显示动画组件呢?因为:**封装的越复杂,使用越简单,往往伴随着功能越不丰富。**比如想让动画一直重复执行,隐式动画组件是无法实现的。
显示动画组件和隐式动画组件中各有一个万能的组件,它们是 AnimatedBuilder 和 TweenAnimationBuilder,当系统中不存在我们想要的动画组件时,可以使用这两个组件,以 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,
);
},
),
);
}
}
AnimatedBuilder 和 TweenAnimationBuilder 本质上和其他动画组件没有区别,只是给了我们更高的灵活性。
如何选取
Flutter 内置的动画组件分为两种:隐式动画组件 和 显示动画组件 ,显示动画组件只封装了 setState
方法,需要开发者创建 AnimationController,并管理 AnimationController。隐式动画组件封装了 AnimationController、Curve、Tween,只需提供给组件动画开始、结束值,其余由系统管理。
隐式动画组件可以完成效果,显示动画组件都可以完成,那么什么时候使用隐式动画组件?什么时候使用显示动画组件?
- 判断你的动画组件是否一直重复,比如一直转圈的loading动画,如果是选择显式动画。
- 判断你的动画组件是否需要多个组件联动,如果是选择显式动画。
- 判断你的动画组件是否需要组合动画,如果是选择显式动画。
- 如果上面三个条件都是否,就选择隐式动画组件,判断是否已经内置动画组件,如果没有,使用 TweenAnimationBuilder,有就直接使用内置动画组件。
- 选择显式动画组件,判断是否已经内置动画组件,如果没有,使用 AnimatedBuilder,有就直接使用内置动画组件。
逻辑图如下:
还有一个简单的区分办法:如果你的动画相对比较简单,动画从一种状态过渡到另一种状态,不需要单独控制 AnimationController,这种情况下,隐式动画组件一般可以就可以实现。
不过也没有必要特别纠结使用隐式动画组件还是显示动画组件,不管使用哪一种,实现效果即可。
- Linux 基础知识
- Spring Cloud Edgware新特性之八:Zuul回退的改进
- Install Django Nginx uWSGI
- Spring Cloud Edgware新特性之七:可选的EnableDiscoveryClient注解
- 【LEETCODE】模拟面试-46. Permutations
- CentOS6 Upgrade Python
- Emacs setup for Go Development
- 【LEETCODE】模拟面试-39. Combination Sum
- Docker系列教程12-使用Maven插件构建Docker镜像
- Linux nohup 用法
- 【LEETCODE】模拟面试-84-Largest Rectangle in Histogram
- Docker系列教程11-使用Nexus管理Docker镜像
- Mac 配置终端环境
- 【LEETCODE】模拟面试-101-Symmetric Tree
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- js实现HTML页面时钟动态显示
- WordPress标题的分隔符-被转义为–的解决办法
- WordPress网站实现下雪特效,云体验下雪的赶脚
- 小知识:后台执行Oracle创建索引免受会话中断影响
- Android:那些关于Fragment生命周期,你了解多少?
- 前端面试(题三)js
- .NET Core EFCore零基础快速入门简单使用
- vscode .vue文件 格式化配置
- 使用代码删除SAP CRM的附件(attachment)数据
- node版本管理器:nvm 和 n区别
- Apache - Hive环境部署与快速入门
- 编译和调试openjdk8
- linux编译openjdk8
- Elasticsearch从入门到放弃:瞎说Mapping
- PostgreSQL 为什么接受大量连接到数据库需要连接池