老孟导读:动画系统是任何一个UI框架的核心功能,也是开发者学习一个UI框架的重中之重,同时也是比较难掌握的一部分,下面咱们就一层一层的揭开 Flutter 动画的面纱。git
任何程序的动画原理都是同样的,即:视觉暂留,视觉暂留又叫视觉暂停,人眼在观察景物时,光信号传入大脑神经,需通过一段短暂的时间,光的做用结束后,视觉形象并不当即消失,这种残留的视觉称“后像”,视觉的这一现象则被称为“视觉暂留”。微信
人眼能保留0.1-0.4秒左右的图像,因此在 1 秒内看到连续的25张图像,人就会感到画面流畅,而 1 秒内看到连续的多少张图像称为 帧率,即 FPS,理论上 达到 24 FPS 画面比较流畅,而Flutter,理论上能够达到 60 FPS。框架
介绍完了动画系统的基本原理,实现一个蓝色盒子大小从 100 变为 200动画效果:ide
class AnimationBaseDemo extends StatefulWidget { @override _AnimationBaseDemoState createState() => _AnimationBaseDemoState(); } class _AnimationBaseDemoState extends State<AnimationBaseDemo> { double _size = 100; @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { setState(() { _size = 200; }); }, child: Container( height: _size, width: _size, color: Colors.blue, alignment: Alignment.center, child: Text('点我变大',style: TextStyle(color: Colors.white,fontSize: 18),), ), ), ); } }
虽然变大了,但并无动画效果,而是直接变大的,想要使其一点点放大须要引入 AnimationController,它是动画控制器,控制动画的启动、中止,还能够获取动画的运行状态,AnimationController 一般在 initState 方法中初始化:学习
class _AnimationBaseDemoState extends State<AnimationBaseDemo> with SingleTickerProviderStateMixin{ double _size = 100; AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController(vsync: this,duration: Duration(milliseconds: 500)); } ... }
这里有两个参数须要设置:动画
vsync
参数,存在vsync
时会防止屏幕外动画消耗没必要要的资源,单个 AnimationController 的时候使用 SingleTickerProviderStateMixin,多个 AnimationController 使用 TickerProviderStateMixin。修改以下:ui
class _AnimationBaseDemoState extends State<AnimationBaseDemo> with SingleTickerProviderStateMixin{ double _size = 100; AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController(vsync: this,duration: Duration(milliseconds: 500)); } @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { _controller.forward(); }, child: Container( height: _size, width: _size, color: Colors.blue, alignment: Alignment.center, child: Text('点我变大',style: TextStyle(color: Colors.white,fontSize: 18),), ), ), ); } @override void dispose() { super.dispose(); _controller.dispose(); } }
点击蓝色盒子的时候再也不直接更改大小,而是执行动画_controller.forward()
。this
另外在State dispose 生命周期中释放 AnimationController。设计
此时点击蓝色盒子发现并不会变大,StatefulWidget 组件改变外观须要调用 setState
,所以给 AnimationController 添加监听:code
_controller = AnimationController(vsync: this,duration: Duration(milliseconds: 500)) ..addListener(() { setState(() { _size = 100+100*_controller.value; }); });
每一帧都会回调addListener ,在此回调中设置蓝色盒子大小,蓝色的大小是由 100 变到 200,而 AnimationController 的值默认是 0 到 1,因此蓝色大小等于 _size = 100+100*_controller.value,运行效果:
这就是 Flutter 中最简单动画的实现方式,其中最重要的就是 AnimationController,_controller.value 是当前动画的值,默认从 0 到 1。也能够经过参数形式设置最大值和最小值:
_controller = AnimationController(vsync: this,duration: Duration(milliseconds: 500),lowerBound: 100,upperBound: 200) ..addListener(() { setState(() { _size = _controller.value; }); })
此时 _controller.value 的值就是从 100变化到 200。
除了使用 addListener 监听每一帧,还能够监听动画状态的变化:
_controller = AnimationController( vsync: this, duration: Duration(milliseconds: 500), lowerBound: 100, upperBound: 200) ..addStatusListener((status) { print('status:$status'); })
动画的状态分为四种:
再来看下动画的控制方法:
forward 和 reverse 方法中都有 from 参数,这个参数的意义是同样的,表示动画今后值开始执行,而再也不是从lowerBound 到 upperBound。好比上面的例子中 from 参数设置 150,那么执行动画时,蓝色盒子瞬间变为 150,而后再慢慢变大到200。
让蓝色盒子大小从 100 到 200,而后再变到 100,再到 200,如此反复:
_controller = AnimationController( vsync: this, duration: Duration(milliseconds: 500), lowerBound: 100, upperBound: 200) ..addStatusListener((AnimationStatus status) { if(status == AnimationStatus.completed){ _controller.reverse(); }else if(status == AnimationStatus.dismissed){ _controller.forward(); } }) ..addListener(() { setState(() { _size = _controller.value; }); });
只需监听动画状态变化,在动画结束后再正向/反向再次执行动画。
虽然上面讲了不少,但只有一个重点 AnimationController。
AnimationController 设置的最小/大值类型是 double,若是动画的变化是颜色要如何处理?
AnimationController 在执行动画期间返回的值是 0 到 1,颜色从蓝色变为红色方法以下:
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 500)) ..addListener(() { setState(() { _color = Color.lerp(_startColor, _endColor, _controller.value); }); });
重点是 Color.lerp 方法,此方法是在两种颜色之间线性插值。
完整代码以下:
class TweenDemo extends StatefulWidget { @override _TweenDemoState createState() => _TweenDemoState(); } class _TweenDemoState extends State<TweenDemo> with SingleTickerProviderStateMixin { AnimationController _controller; Color _startColor = Colors.blue; Color _endColor = Colors.red; Color _color = Colors.blue; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 500)) ..addListener(() { setState(() { _color = Color.lerp(_startColor, _endColor, _controller.value); }); }); } @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { _controller.forward(); }, child: Container( height: 100, width: 100, color: _color, alignment: Alignment.center, child: Text( '点我变色', style: TextStyle(color: Colors.white, fontSize: 18), ), ), ), ); } @override void dispose() { super.dispose(); _controller.dispose(); } }
Flutter 中把这种从 0 -> 1 转换为 蓝色 -> 红色 行为称之为 Tween(映射)。
使用 Tween 完成动画 蓝色 -> 红色:
class _TweenDemoState extends State<TweenDemo> with SingleTickerProviderStateMixin { AnimationController _controller; Animation<Color> _animation; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 500)) ..addListener(() { setState(() {}); }); _animation = ColorTween(begin: Colors.blue, end: Colors.red).animate(_controller); } @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { _controller.forward(); }, child: Container( height: 100, width: 100, color: _animation.value, alignment: Alignment.center, child: Text( '点我变色', style: TextStyle(color: Colors.white, fontSize: 18), ), ), ), ); } @override void dispose() { super.dispose(); _controller.dispose(); } }
效果和上面是同样的。
Tween 仅仅是映射,动画的控制依然由 AnimationController 控制,所以须要 Tween.animate(_controller) 将控制器传递给Tween。
系统提供了大量的 Tween:
基本上经常使用的属性都包含了其对应的 Tween,看一下 ColorTween 的源代码实现:
本质上也是使用 Color.lerp 实现的。
动画中还有一个重要的概念就是 Curve,即动画执行曲线。Curve 的做用和 Android 中的 Interpolator(差值器)是同样的,负责控制动画变化的速率,通俗地讲就是使动画的效果可以以匀速、加速、减速、抛物线等各类速率变化。
蓝色盒子大小 100 变大到 200,动画曲线设置为 bounceIn(弹簧效果) :
class CurveDemo extends StatefulWidget { @override _CurveDemoState createState() => _CurveDemoState(); } class _CurveDemoState extends State<CurveDemo> with SingleTickerProviderStateMixin { AnimationController _controller; Animation _animation; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 1000)) ..addListener(() { setState(() {}); }); _animation = Tween(begin: 100.0, end: 200.0) .chain(CurveTween(curve: Curves.bounceIn)) .animate(_controller); } @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { _controller.forward(); }, child: Container( height: _animation.value, width: _animation.value, color: Colors.blue, alignment: Alignment.center, child: Text( '点我变大', style: TextStyle(color: Colors.white, fontSize: 18), ), ), ), ); } @override void dispose() { super.dispose(); _controller.dispose(); } }
动画加上Curve 后,AnimationController 的最小/大值必须是 [0,1]之间,例以下面的写法就是错误的:
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 1000),lowerBound: 100.0,upperBound: 200.0) ..addListener(() { setState(() {}); }); _animation = CurveTween(curve: Curves.bounceIn).animate(_controller);
抛出以下异常:
正确写法:
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 1000)) ..addListener(() { setState(() {}); }); _animation = Tween(begin: 100.0, end: 200.0) .chain(CurveTween(curve: Curves.bounceIn)) .animate(_controller);
系统已经提供了38种经常使用到动画曲线:
linear
decelerate
bounceIn
bounceOut
elasticIn
其他动画效果能够官方文档查看。
一般状况下,这些曲线可以知足 99.99% 的需求,不少时候设计也就是告诉你动画 先快后慢 或者 先慢后快,因此选个相似的就能够了,但有一些 特别 的设计非要一个系统没有的动画曲线,要怎么办?
其实自定义一个动画曲线难点在 数学 上,怎么把数学公式用代码实现才是难点。
下面是一个 楼梯效果 的动画曲线:
自定义动画曲线须要继承 Curve 重写 transformInternal 方法便可:
class _StairsCurve extends Curve { @override double transformInternal(double t) { return t; } }
直接返回 t 其实就是线性动画,即 Curves.linear,实现楼梯效果动画代码以下:
class _StairsCurve extends Curve { //阶梯的数量 final int num; double _perStairY; double _perStairX; _StairsCurve(this.num) { _perStairY = 1.0 / (num - 1); _perStairX = 1.0 / num; } @override double transformInternal(double t) { return _perStairY * (t / _perStairX).floor(); } }
修改开始处的案例,使用此曲线:
_animation = Tween(begin: 100.0, end: 200.0) .chain(CurveTween(curve: _StairsCurve(5))) .animate(_controller);
动画系统的核心是 AnimationController,并且是不可或缺的,动画中必须有 AnimationController,而 Tween 和 Curve 则是对 AnimationController 的补充, Tween 实现了将 AnimationController [0,1]的值映射为其余类型的值,好比颜色、样式等,Curve 是 AnimationController 动画执行曲线,默认是线性运行。
将 AnimationController 、 Tween 、Curve 进行关联的方式:
AnimationController _controller; Animation _animation; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 1000)) ..addListener(() { setState(() {}); }); _animation = Tween(begin: 100.0, end: 200.0) .animate(_controller); }
或者:
_animation = _controller.drive(Tween(begin: 100.0, end: 200.0));
加入 Curve :
_animation = Tween(begin: 100.0, end: 200.0) .chain(CurveTween(curve: Curves.linear)) .animate(_controller);
或者:
_animation = _controller .drive(CurveTween(curve: Curves.linear)) .drive(Tween(begin: 100.0, end: 200.0));
只须要 Curve :
_animation = CurveTween(curve: Curves.linear) .animate(_controller);
或者
_animation = _controller.drive(CurveTween(curve: Curves.linear));
一个 AnimationController 能够对应多个 Animation(Tween 或者 Curve),StatefulWidget 组件能够包含多个 AnimationController ,但 SingleTickerProviderStateMixin 须要修改成 TickerProviderStateMixin,改变颜色和大小,由两个 AnimationController 控制:
class MultiControllerDemo extends StatefulWidget { @override _MultiControllerDemoState createState() => _MultiControllerDemoState(); } class _MultiControllerDemoState extends State<MultiControllerDemo> with TickerProviderStateMixin { AnimationController _sizeController; AnimationController _colorController; Animation<double> _sizeAnimation; Animation<Color> _colorAnimation; @override void initState() { super.initState(); _sizeController = AnimationController(vsync: this, duration: Duration(milliseconds: 2000)) ..addListener(() { setState(() {}); }); _sizeAnimation = _sizeController .drive(CurveTween(curve: Curves.linear)) .drive(Tween(begin: 100.0, end: 200.0)); _colorController = AnimationController(vsync: this, duration: Duration(milliseconds: 1000)) ..addListener(() { setState(() {}); }); _colorAnimation = _colorController .drive(CurveTween(curve: Curves.bounceIn)) .drive(ColorTween(begin: Colors.blue, end: Colors.red)); } @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { _sizeController.forward(); _colorController.forward(); }, child: Container( height: _sizeAnimation.value, width: _sizeAnimation.value, color: _colorAnimation.value, alignment: Alignment.center, child: Text( '点我变化', style: TextStyle(color: Colors.white, fontSize: 18), ), ), ), ); } @override void dispose() { super.dispose(); _sizeController.dispose(); _colorController.dispose(); } }
AnimationController 、Tween 、Curve 是整个动画的基础,Flutter 系统封装了大量了动画组件,但这些组件也是基于此封装的,由于深刻了解这三部分比学习使用动画组件更重要,再次对这3个进行总结:
完成一个动画效果的过程以下:
若是你发现阅读完此篇文章仍是感受不会写动画,不要灰心,这是正常的,第一次想了解这些抽象的概念仍是比较困难的,若是你有其余平台的相关经验,那会好不少,对于动画,想要掌握我的认为只有一个方法就是 多写。
后面会介绍动画组件基础使用、实现原理、高级动画以及自定义动画,把每个动画组件的用法都亲自手写一遍(而不是复制黏贴),回过头来在看这篇文章,会有不同的感受。
老孟Flutter博客地址(330个控件用法):http://laomengit.com
欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:
![]() |
![]() |