动画做为产品的重要组成部分,是提高用户体验的重要方式,一个恰当的动画不只可以缓解用户由于等待而带来的情绪焦躁,还会增长应用的总体用户体验。所以,在应用中增长动画的相关功能,能够加强用户的粘性。app
不论是Android平台仍是iOS平台,咱们在使用应用时都能看到一些炫酷的动画效果。做为移动应用的重要组成部分,动画是提升用户体验的重要手段,一个恰当的动画,不只可以缓解用户由于等待而带来的情绪问题,还会提高用户使用的体验。
事实上,不论是什么视图框架,动画的实现原理都是相同的,即在一段有限的时间内,屡次快速地改变视图外观来实现一个连续播放的效果。视图的一次改变即称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率FPS(Frame Per Second缩写),即每秒的动画帧数。很明显,帧率越高则动画就会越流畅。
目前,大多数设备的屏幕刷新频率能够到达60Hz,而对于人眼来讲,动画帧率超过16FPS就认为是流畅的,超过32FPS基本就感觉不到任何卡顿。因为动画的每一帧都须要改变视图的输出,因此在一个时间段内连续的改变视图输出是比较耗费资源的,对设备的软硬件系统要求也比较高。做为衡量一个视图框架优劣的标准,Flutter框架在理想状况下是能够实现60FPS的,这和原生应用的帧率标准是基本是持平的。
同时,为了方便开发者建立并使用动画,不一样的视图框架对动画都进行了高度的抽象和封装,好比在Android开发中,可使用XML来描述一个动画而后再设置给一个视图对象。一样,Flutter也对动画进行了高度的抽象,而且提供了Animation、Curve、Controller、Tween等四个动画对象。
Animation是Flutter动画的核心抽象类,包含动画的当前值和状态两个属性。AnimationController是Animation的控制器,动画的开始、结束、中止、反向均由它控制,能够经过Listener和StatusListener来管理动画状态的改变。框架
在Flutter中,学习动画相关的开发,其实就是围绕Animation、Curve、Controller、Tween等四个动画对象来展开的。less
在Flutter中,Animation是实现动画的核心类,Animation的主要做用就是保存动画的插值和状态,它自己与视图渲染没有任何关系。Animation对象则是一个能够在一段时间内依次生成一个区间值的类,其输出值能够是线性的、曲线的,能够是一个步进函数或者任何其余曲线函数等,由Curve来决定。Animation的核心源码以下:ide
abstract class Animation<T> extends Listenable implements ValueListenable<T> { const Animation(); // 添加动画监听器 @override void addListener(VoidCallback listener); // 移除动画监听器 @override void removeListener(VoidCallback listener); // 添加动画状态监听器 void addStatusListener(AnimationStatusListener listener); // 移除动画状态监听器 void removeStatusListener(AnimationStatusListener listener); // 获取动画当前状态 AnimationStatus get status; // 获取动画当前的值 @override T get value;
Animation是一个抽象类,Widget能够直接将这些动画合并到本身的build方法中来读取它们的当前值或者监听它们的状态变化。Animation提供了addListener和addStatusListener两个方法来监听动画帧的变化。函数
addListener
addListener方法用于给Animation对象添加帧监听器,每一帧都会被调用,当帧监听器监听到状态发生改变后会调用setState()来触发视图的重建。这意味着:性能
addStatusListener
addStatusListener方法用于给Animation对象添加动画状态改变监听器,动画开始、结束、正向或反向时会调用状态改变的监听器。这意味着:学习
AnimationController,即动画控制器,Animation是一个抽象类,并不能用来直接建立对象并实现动画,它的主要用于控制动画的开始、结束、中止、反向等操做。AnimationController是Animation的一个子类,默认状况下,AnimationController会在给定的时间段内以线性的方式生成从0.0到1.0的数字。它的源码以下:优化
class AnimationController extends Animation<double> with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin { AnimationController({ // 初始化值 double value, // 动画执行的时间 this.duration, // 反向动画执行的时间 this.reverseDuration, // 最小值 this.lowerBound = 0.0, // 最大值 this.upperBound = 1.0, // 刷新率ticker的回调(看下面详细解析) @required TickerProvider vsync, }) }
其中,AnimationController有一个必传的参数vsync,那么AnimationController有什么做用呢?以前我讲过关于Flutter的渲染闭环,Flutter每次渲染一帧画面以前都须要等待一个vsync信号。这里也是为了监听vsync信号,当Flutter开发的应用程序再也不接受同步信号时(好比锁屏或退到后台),那么继续执行动画会消耗性能,开发中比较常见的解决方法是将SingleTickerProviderStateMixin混入到State的定义中。例如,下面是一个比较简单的数字自动增长动画的示例。动画
import 'package:flutter/material.dart'; import 'package:gc_data_app/utils/utils.dart'; class AnimText extends StatefulWidget { final int number; final int duration; final Color fontColor; final double fontSize; const AnimText({ Key key, this.number, this.duration, this.fontColor, this.fontSize, }) : super(key: key); @override State<StatefulWidget> createState() { return AnimState(); } } class AnimState extends State<AnimText> with SingleTickerProviderStateMixin { AnimationController controller; Animation animation; var begin=0; @override void initState() { super.initState(); controller = AnimationController( vsync: this, duration: Duration(milliseconds: widget.duration)); final Animation curve=CurvedAnimation(parent: controller,curve: Curves.linear); animation = IntTween(begin: begin, end: widget.number).animate(curve)..addStatusListener((status) { if(status==AnimationStatus.completed){ // controller.reverse(); } }); } @override Widget build(BuildContext context) { controller.forward(); return AnimatedBuilder( animation: controller, builder: (context,child){ return Container( child:Text(Utils.formatMoney(animation.value), style: TextStyle(fontSize: widget.fontSize, color: widget.fontColor,fontWeight: FontWeight.bold)), ); } , ); } @override void dispose() { controller.dispose(); super.dispose(); } }
CurvedAnimation是Animation的一个实现类,它的目的是为了给AnimationController增长动画曲线。一般,动画过程能够是匀速的、匀加速的或者先加速后减速等。Flutter经过Curve来描述动画过程,咱们能够把匀速动画称为线性动画,把非匀速动画称为非线性动画。ui
CurvedAnimation能够将AnimationController和Curve结合起来,生成一个新的Animation对象。例如:
class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> { CurvedAnimation({ // 一般传入一个AnimationController @required this.parent, // Curve类型的对象 @required this.curve, this.reverseCurve, }); }
Curve类型的对象的有一些常量Curves能够直接使用,经常使用的有以下一些:
默认状况下,AnimationController对象的取值范围是[0.0,1.0],若是须要给动画设置不一样的范围或者类型的值时,可使用Tween来定义并生成不一样范围或类型的值。Tween的源码很是简单,传入两个值便可,以下所示。
class Tween<T extends dynamic> extends Animatable<T> { Tween({ this.begin, this.end }); }
Tween继承自Animatable<T>,而不是继承自Animation<T>,Animatable是一个控制动画类型的类,主要定义了动画值的映射规则。虽然,Animatable和Animation有不少类似之处,但它的类型能够是除double的其余类型。例如,下面是使用ColorTween实现颜色渐变的过渡动画的例子。
Tween colorTween =new ColorTween(begin: Colors.transparent, end: Colors.black54);
Tween也有一些子类,好比ColorTween、BorderTween,能够针对动画或者边框来设置动画的值。
和原平生台的动画开发同样,Flutter的动画开发也有必定的规则,实际使用时,只须要按照遵循步骤便可。通用的步骤以下:
1.建立AnimationController和Animation;
2.设置动画的类型,监听动画执行
3.销毁动画
例如,下面是Flutter动画的基本使用示例,代码以下:
import 'package:demos/page/anim_page.dart'; import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(), ); } } class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Flutter 动画'), ), body: HeartAnimationWidget(key: animKey), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: () { if (!animKey.currentState.controller.isAnimating) { animKey.currentState.controller.forward(); } else { animKey.currentState.controller.stop(); } }, ), ); } } GlobalKey<_HeartAnimationPageState> animKey = GlobalKey(); class HeartAnimationPage extends StatefulWidget { HeartAnimationPage({Key key}): super(key: key); @override _HeartAnimationPageState createState() => _HeartAnimationPageState(); } class _HeartAnimationPageState extends State<HeartAnimationPage> with SingleTickerProviderStateMixin { AnimationController controller; Animation<double> animation; @override void initState() { super.initState(); // 1.建立AnimationController controller = AnimationController(duration: Duration(seconds: 1), vsync: this); // 2.动画添加Curve效果 animation = CurvedAnimation(parent: controller, curve: Curves.elasticInOut, reverseCurve: Curves.easeOut); // 3.监听动画 animation.addListener(() { setState(() {}); }); // 4.控制动画的翻转 animation.addStatusListener((status) { if (status == AnimationStatus.completed) { controller.reverse(); } else if (status == AnimationStatus.dismissed) { controller.forward(); } }); // 5.设置值的范围 animation = Tween(begin: 50.0, end: 120.0).animate(controller); } @override Widget build(BuildContext context) { return Center( child: Icon(Icons.favorite, color: Colors.red, size: animation.value,), ); } @override void dispose() { controller.dispose(); super.dispose(); } }
运行上面的代码,当点击案例后执行一个心跳动画,能够反复执行,再次点击能够暂停和从新开始动画,运行效果以下图所示。
经过addListener()和setState()来更新视图是动画实现的通用的作法,但缺点是须要在每一个动画中都添加监听函数,代码比较冗余,而且调用setState()方法意味着整个State类中的build方法就会被从新构建,性能损耗比较严重。所以,官方推荐使用AnimatedWidget类来实现一样的动画效果,由于AnimatedWidget类简化了addListener()和setState()的调用流程,并隐藏了底层额实现细节。
对于上面的示例,咱们西安建立一个继承自AnimatedWidget的Widget,以下所示。
class HeatAnimationWidget extends AnimatedWidget { HeatAnimationWidget(Animation animation): super(listenable: animation); @override Widget build(BuildContext context) { Animation animation = listenable; return Icon(Icons.favorite, color: Colors.red, size: animation.value,); } }
而后,咱们对HeartAnimationPage的代码进行以下修改。
class HeartAnimationPage extends StatefulWidget { HeartAnimationPage({Key key}): super(key: key); @override _HeartAnimationPageState createState() => _HeartAnimationPageState(); } class _HeartAnimationPageState extends State<HeartAnimationPage> with SingleTickerProviderStateMixin { AnimationController controller; Animation<double> animation; @override void initState() { super.initState(); // 1.建立AnimationController controller = AnimationController(duration: Duration(seconds: 1), vsync: this); // 2.动画添加Curve效果 animation = CurvedAnimation(parent: controller, curve: Curves.elasticInOut, reverseCurve: Curves.easeOut); // 3.监听动画 // 4.控制动画的翻转 animation.addStatusListener((status) { if (status == AnimationStatus.completed) { controller.reverse(); } else if (status == AnimationStatus.dismissed) { controller.forward(); } }); // 5.设置值的范围 animation = Tween(begin: 50.0, end: 120.0).animate(controller); } @override Widget build(BuildContext context) { return Center( child: HeatAnimationWidget(animation), ); } @override void dispose() { controller.dispose(); super.dispose(); } }
经过AnimatedWidget类,咱们能够从动画中分离出组件,从而将动画和组件分离开来,不过动画的渲染过程仍然在AnimatedWidget中执行。若是想要将动画的渲染过程分离出来,可使用AnimatedBuilder类,与AnimatedWidget的做用相似,AnimatedBuilder能够自动监听Animation的变化,而后根据须要自动刷新视图。
所以,在上面的示例中,咱们可使用AnimatedBuilder进行以下的优化:
class HeartAnimationPage extends StatefulWidget { HeartAnimationPage({Key key}): super(key: key); @override _HeartAnimationPageState createState() => _HeartAnimationPageState(); } class _HeartAnimationPageState extends State<HeartAnimationPage> with SingleTickerProviderStateMixin { AnimationController controller; Animation<double> animation; @override void initState() { super.initState(); // 1.建立AnimationController controller = AnimationController(duration: Duration(seconds: 1), vsync: this); // 2.动画添加Curve效果 animation = CurvedAnimation(parent: controller, curve: Curves.elasticInOut, reverseCurve: Curves.easeOut); // 3.监听动画 // 4.控制动画的翻转 animation.addStatusListener((status) { if (status == AnimationStatus.completed) { controller.reverse(); } else if (status == AnimationStatus.dismissed) { controller.forward(); } }); // 5.设置值的范围 animation = Tween(begin: 50.0, end: 120.0).animate(controller); } @override Widget build(BuildContext context) { return Center( child: AnimatedBuilder( animation: animation, builder: (ctx, child) { return Icon(Icons.favorite, color: Colors.red, size: animation.value,); }, ) ); } @override void dispose() { controller.dispose(); super.dispose(); } }
除了上面介绍的动画外,Flutter还提供了交错动画和Hero动画。