一,概述   

  Flutter动画库的核心类是Animation对象,它生成指导动画的值,Animation对象指导动画的当前状态(例如,是开始、停止还是向前或者向后移动),但它不知道屏幕上显示的内容。动画类型分为两类:

  1. 补简动画(Tween),定义了开始点和结束点、时间线以及定义转换时间和速度的曲线。然后由框架计算如何从开始点过渡到结束点。Tween是一个无状态(stateless)对象,需要beginend值。Tween的唯一职责就是定义从输入范围到输出范围的映射。输入范围通常为0.0到1.0,但这不是必须的。
  2. 基于物理动画,运动被模拟与真实世界行为相似,例如,当你掷球时,它何处落地,取决于抛球速度有多快、球有多重、距离地面有多远。类似地,将连接在弹簧上的球落下(并弹起)与连接到绳子的球放下的方式也是不同。

  在Flutter中的动画系统基于Animation对象的。widget可以在build函数中读取Animation对象的当前值,并且可以监听动画的状态改变。  

二,Flutter动画介绍

  • Animation

  Animation 是 Flutter 动画库中的核心类,它会插入指导动画生成的值。 Animation 对象知道一个动画当前的状态(例如开始、 停止、 播放、 回放), 但它不知道屏幕上绘制的是什么, 因为 Animation 对象只是提供一个值表示当前需要展示的动画, UI 如何绘制出图形完全取决于 UI 自身如何在渲染和 build() 方法里处理这个值, 当然也可以不做处理。 Animation<double>是一个比较常用的Animation类, 泛型也可以支持其它的类型,比如: Animation<Color>或 Animation<Size>Animation 对象就是会在一段时间内依次生成一个区间之间值的类, 它的输出可以是线性的、曲线的、一个步进函数或者任何其他可以设计的映射 比如:CurvedAnimation

  • AnimationController

  AnimationController 是一个动画控制器, 它控制动画的播放状态, 如例子里面的: controller.forward()就是控制动画"向前"播放。 所以构建 AnimationController 对象之后动画并没有立刻开始执行。 在默认情况下, AnimationController 会在给定的时间内线性地生成从 0.0 到 1.0 之间的数字。 AnimationController 是一种特殊的 Animation 对象了, 它父类其实是一个 Animation<double>, 当硬件准备好需要一个新的帧的时候它就会产生一个新的值。 由于 AnimationController 派生自 Animation <double>,因此可以在需要 Animation 对象的任何地方使用它。 但是 AnimationController 还有其他的方法来控制动画的播放, 例如前面提到的 .forward()方法启动动画。

  AnimationController 生成的数字(默认是从 0.0 到 1.0) 是和屏幕刷新有关, 前面也提到它会在硬件需要一个新帧的时候产生新值。 因为屏幕一般都是 60 帧/秒, 所以它也通常一秒内生成 60 个数字。 每个数字生成之后, 每个 Animation 对象都会调用绑定的监听器对象。

  • Tween

  Tween 本身表示的就是一个 Animation 对象的取值范围, 只需要设置开始和结束的边界值(值也支持泛型)。 它唯一的工作就是定义输入范围到输出范围的映射, 输入一般是 AnimationController 给出的值 0.0~1.0。 看下面的例子, 我们就能知道 animationvalue 是怎么样通过 AnimationController 生成的值映射到 Tween 定义的取值范围里面的。

  1. Tween.animation通过传入 aniamtionController 获得一个_AnimatedEvaluation 类型的 animation 对象(基类为 Animation), 并且将 aniamtionControllerTween 对象传入了 _AnimatedEvaluation 对象。

    1. animation = new Tween(begin: 0.0, end: 300.0).animate(controller)
    2. ...
    3. Animation<T> animate(Animation<double> parent) {
    4. return _AnimatedEvaluation<T>(parent, this);
    5. }
  2. animation.value方法即是调用 _evaluatable.evaluate(parent)方法, 而 _evaluatableparent 分别为 Tween 对象AnimationController 对象
    1. T get value => _evaluatable.evaluate(parent);
    2. ....
    3. class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {
    4. _AnimatedEvaluation(this.parent, this._evaluatable);
    5. ....
  3. 这里的 animation 其实就是前面的 AnimationController 对象, transform 方法里面的 animation.value则就是 AnimationController 线性生成的 0.0~1.0 直接的值。 在 lerp 方法里面我们可以看到这个 0.0~1.0 的值被映射到了 beginend 范围内了。
    1. T evaluate(Animation<double> animation) => transform(animation.value);
    2.  
    3. T transform(double t) {
    4. if (t == 0.0)
    5. return begin;
    6. if (t == 1.0)
    7. return end;
    8. return lerp(t);
    9. }
    10.  
    11. T lerp(double t) {
    12. assert(begin != null);
    13. assert(end != null);
    14. return begin + (end - begin) * t;
    15. }
  • Flutter 的"时钟"

  那么 Flutter 是怎么样让这个动画在规定时间不断地绘制的呢?

  1. 首先看 Widget 引入的 SingleTickerProviderStateMixin 类。SingleTickerProviderStateMixin 是以 with 关键字引入的, 这是 dart 语言的 mixin 特性, 可以理解成"继承", 所以 widget 相当于是继承了SingleTickerProviderStateMixin。 所以在 AnimationController 对象的构造方法参数 vsync: this, 我们看到了这个类的使用。 从 "vsync" 参数名意为"垂直帧同步"可以看出, 这个是绘制动画帧的"节奏器"。

    1. AnimationController({
    2. double value,
    3. this.duration,
    4. this.debugLabel,
    5. this.lowerBound = 0.0,
    6. this.upperBound = 1.0,
    7. this.animationBehavior = AnimationBehavior.normal,
    8. @required TickerProvider vsync,
    9. }) : assert(lowerBound != null),
    10. assert(upperBound != null),
    11. assert(upperBound >= lowerBound),
    12. assert(vsync != null),
    13. _direction = _AnimationDirection.forward {
    14. _ticker = vsync.createTicker(_tick);
    15. _internalSetValue(value ?? lowerBound);
    16. }
  2. AnimationController 的构造方法中, SingleTickerProviderStateMixin 的父类 TickerProvider 会创建一个 Ticker, 并将_tick(TickerCallback 类型)回调方法绑定到了 这个 Ticker, 这样 AnimationController 就将回调方法 _tickTicker 绑定了。
    1. @protected
    2. void scheduleTick({ bool rescheduling = false }) {
    3. assert(!scheduled);
    4. assert(shouldScheduleTick);
    5. _animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
    6. }
  3. Ticker 会在 start 函数内将_tick 被绑定到 SchedulerBinding 的帧回调方法内。 返回的_animationIdSchedulerBinding 给定的下一个动作回调的 ID, 可以根据_animationId 来取消 SchedulerBinding 上绑定的回调。

    SchedulerBinding 则是在构造方法中将自己的 _handleBeginFrame 函数和 windowonBeginFrame 绑定了回调。 这个回调会在屏幕需要准备显示帧之前回调。

    再回到 AnimationController 看它是如何控制 Animation 的值的。

    1. void _tick(Duration elapsed) {
    2. _lastElapsedDuration = elapsed;
    3. final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
    4. assert(elapsedInSeconds >= 0.0);
    5. _value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
    6. if (_simulation.isDone(elapsedInSeconds)) {
    7. _status = (_direction == _AnimationDirection.forward) ?
    8. AnimationStatus.completed :
    9. AnimationStatus.dismissed;
    10. stop(canceled: false);
    11. }
    12. notifyListeners();
    13. _checkStatusChanged();
    14. }
  4. AnimationController 的回调当中, 会有一个 Simulation 根据动画运行了的时间(elapsed) 来计算当前的的_value 值, 而且这个值还需要处于 Animation 设置的区间之内。 除了计算_value 值之外, 该方法还会更新 Animation Status 的状态, 判断是否动画已经结束。 最后通过 notifyListeners_checkStatusChanged 方法通知给监听器 value 和 AnimationStatus 的变化。 监听 AnimationStatus 值的变化有一个专门的注册方法 addStatusListener

    通过监听 AnimationStatus, 在动画开始或者结束的时候反转动画, 就达到了动画循环播放的效果。

    1. ...
    2. animation.addStatusListener((status) {
    3. if (status == AnimationStatus.completed) {
    4. controller.reverse();
    5. } else if (status == AnimationStatus.dismissed) {
    6. controller.forward();
    7. }
    8. });
    9. controller.forward();
    10.  
    11. ...
  5. 回顾一下这个动画绘制调用的顺序就是, window 调用 SchedulerBinding_handleBeginFrame 方法, SchedulerBinding 调用 Ticker 的_tick 方法, Ticker 调用 AnimationController 的_tick 的方法, AnimationContoller 通知监听器, 而监听器调用 widget 的 setStatus 方法来调用 build 更新, 最后 build 使用了 Animation 对象当前的值来绘制动画帧。

    看到这里会有一个疑惑, 为什么监听器是注册在 Animation 上的, 监听通知反而由 AnimationController 发送?

    还是看源码吧。

    1. Animation<T> animate(Animation<double> parent) {
    2. return _AnimatedEvaluation<T>(parent, this);
    3. }
    4.  
    5. class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {
    6. _AnimatedEvaluation(this.parent, this._evaluatable);
    7. }
    8.  
    9. mixin AnimationWithParentMixin<T> {
    10. Animation<T> get parent;
    11. /// Listeners can be removed with [removeListener].
    12. void addListener(VoidCallback listener) => parent.addListener(listener);
    13. }
  6. 首先 Animation 对象是由 Tweenanimate 方法生成的, 它传入了 AnimationController(Animation 的子类) 参数 作为 parent 参数, 然后我们发现返回的 _AnimatedEvaluation<T>泛型对象 使用 mixin "继承" 了 AnimationWithParentMixin<double>, 最后我们看到 Animation 作为 AnimationWithParentMixin 的"子类"实现的 addListener 方法其实是将监听器注册到 parent 对象上了, 也就是 AnimationController

三,动画示例

  • 示例一

    1. import 'package:flutter/material.dart';
    2. import 'package:flutter/animation.dart';
    3.  
    4. void main() {
    5. //运行程序
    6. runApp(LogoApp());
    7. }
    8.  
    9. class LogoApp extends StatefulWidget{
    10. @override
    11. State<StatefulWidget> createState(){
    12. return new _LogoAppState();
    13. }
    14. }
    15.  
    16. //logo
    17. Widget ImageLogo = new Image(
    18. image: new AssetImage('images/logo.jpg'),
    19. );
    20.  
    21. //with 是dart的关键字,混入的意思,将一个或者多个类的功能添加到自己的类无需继承这些类
    22. //避免多重继承问题
    23. //SingleTickerProviderStateMixin 初始化 animation 和 Controller的时候需要一个TickerProvider类型的参数Vsync
    24. //所依混入TickerProvider的子类
    25. class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{
    26. //动画的状态,如动画开启,停止,前进,后退等
    27. Animation<double> animation;
    28. //管理者animation对象
    29. AnimationController controller;
    30. @override
    31. void initState() {
    32. // TODO: implement initState
    33. super.initState();
    34. //创建AnimationController
    35. //需要传递一个vsync参数,存在vsync时会防止屏幕外动画(
    36. //译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
    37. controller = new AnimationController(
    38. //时间是3000毫秒
    39. duration: const Duration(
    40. milliseconds:
    41. ),
    42. //vsync 在此处忽略不必要的情况
    43. vsync: this,
    44. );
    45. //补间动画
    46. animation = new Tween(
    47. //开始的值是0
    48. begin: 0.0,
    49. //结束的值是200
    50. end : 200.0,
    51. ).animate(controller)//添加监听器
    52. ..addListener((){
    53. //动画值在发生变化时就会调用
    54. setState(() {
    55.  
    56. });
    57. });
    58. //只显示动画一次
    59. controller.forward();
    60. }
    61. @override
    62. Widget build(BuildContext context){
    63. return new MaterialApp(
    64. theme: ThemeData(
    65. primarySwatch: Colors.red
    66.  
    67. ),
    68. home: new Scaffold(
    69. appBar: new AppBar(
    70. title: Text("动画demo"),
    71. ),
    72. body:new Center(
    73. child: new Container(
    74. //宽和高都是根据animation的值来变化
    75. height: animation.value,
    76. width: animation.value,
    77. child: ImageLogo,
    78. ),
    79. ),
    80. ),
    81. );
    82. }
    83.  
    84. @override
    85. void dispose() {
    86. // TODO: implement dispose
    87. super.dispose();
    88. //资源释放
    89. controller.dispose();
    90. }
    91. }

    上面实现了图像在3000毫秒间从宽高是0变化到宽高是200,主要分为六部

    1. 混入SingleTickerProviderStateMixin,为了传入vsync对象
    2. 初始化AnimationController对象
    3. 初始化Animation对象,并关联AnimationController对象
    4. 调用AnimationControllerforward开启动画
    5. widget根据Animationvalue值来设置宽高
    6. widgetdispose()方法中调用释放资源

    最终效果如下:
    注意:上面创建Tween用了Dart语法的级联符号

    1. animation = tween.animate(controller)
    2. ..addListener(() {
    3. setState(() {
    4. // the animation object’s value is the changed state
    5. });
    6. });

    等价于下面代码:

    1. animation = tween.animate(controller);
    2. animation.addListener(() {
    3. setState(() {
    4. // the animation object’s value is the changed state
    5. });
    6. });

    1.1.AnimatedWidget简化

      使用AnimatedWidget对动画进行简化,使用AnimatedWidget创建一个可重用动画的widget,而不是用addListener()setState()来给widget添加动画。AnimatedWidget类允许从setState()调用中的动画代码中分离出widget代码。AnimatedWidget不需要维护一个State对象了来保存动画。

    1. import 'package:flutter/material.dart';
    2. import 'package:flutter/animation.dart';
    3.  
    4. void main() {
    5. //运行程序
    6. runApp(LogoApp());
    7. }
    8.  
    9. class LogoApp extends StatefulWidget{
    10. @override
    11. State<StatefulWidget> createState(){
    12. return new _LogoAppState();
    13. }
    14. }
    15.  
    16. //logo
    17. Widget ImageLogo = new Image(
    18. image: new AssetImage('images/logo.jpg'),
    19. );
    20.  
    21. //抽象出来
    22. class AnimatedLogo extends AnimatedWidget{
    23. AnimatedLogo({
        Key key,Animation<double> animation
       }):super(key:key,listenable:animation);
    24.  
    25. @override
    26. Widget build(BuildContext context){
    27. final Animation<double> animation = listenable;
    28. return new MaterialApp(
    29. theme: ThemeData(
    30. primarySwatch: Colors.red
    31. ),
    32. home: new Scaffold(
    33. appBar: new AppBar(
    34. title: Text("动画demo"),
    35. ),
    36. body:new Center(
    37. child: new Container(
    38. //宽和高都是根据animation的值来变化
    39. height: animation.value,
    40. width: animation.value,
    41. child: ImageLogo,
    42. ),
    43. ),
    44. ),
    45. );
    46. }
    47. }
    48.  
    49. //with 是dart的关键字,混入的意思,将一个或者多个类的功能添加到自己的类无需继承这些类
    50. //避免多重继承问题
    51. //SingleTickerProviderStateMixin 初始化 animation 和 Controller的时候需要一个TickerProvider类型的参数Vsync
    52. //所依混入TickerProvider的子类
    53. class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{
    54. //动画的状态,如动画开启,停止,前进,后退等
    55. Animation<double> animation;
    56. //管理者animation对象
    57. AnimationController controller;
    58. @override
    59. void initState() {
    60. // TODO: implement initState
    61. super.initState();
    62. //创建AnimationController
    63. //需要传递一个vsync参数,存在vsync时会防止屏幕外动画(
    64. //译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
    65. controller = new AnimationController(
    66. //时间是3000毫秒
    67. duration: const Duration(
    68. milliseconds:
    69. ),
    70. //vsync 在此处忽略不必要的情况
    71. vsync: this,
    72. );
    73. //补间动画
    74. animation = new Tween(
    75. //开始的值是0
    76. begin: 0.0,
    77. //结束的值是200
    78. end : 200.0,
    79. ).animate(controller);//添加监听器
    80. //只显示动画一次
    81. controller.forward();
    82. }
    83.  
    84. @override
    85. Widget build(BuildContext context){
    86. return AnimatedLogo(animation: animation);
    87. }
    88.  
    89. @override
    90. void dispose() {
    91. // TODO: implement dispose
    92. super.dispose();
    93. //资源释放
    94. controller.dispose();
    95. }
    96. }

    可以发现AnimatedWidget中会自动调用addListenersetState()_LogoAppStateAnimation对象传递给基类并用animation.value设置Image宽高。

    1.2.监视动画

    在平时开发,我们知道,很多时候都需要监听动画的状态,好像完成、前进、倒退等。在Flutter中可以通过addStatusListener()来得到这个通知,以下代码添加了动画状态

    1. //补间动画
    2. animation = new Tween(
    3. //开始的值是0
    4. begin: 0.0,
    5. //结束的值是200
    6. end : 200.0,
    7. ).animate(controller)
    8. //添加动画状态
    9. ..addStatusListener((state){
    10. return print('$state');
    11. });//添加监听器

    运行代码会输出下面结果:

    1. I/flutter (): AnimationStatus.forward //动画开始
    2. Syncing files to device KNT AL10...
    3. I/zygote64(): Do partial code cache collection, code=30KB, data=25KB
    4. I/zygote64(): After code cache collection, code=30KB, data=25KB
    5. I/zygote64(): Increasing code cache capacity to 128KB
    6. I/flutter (): AnimationStatus.completed//动画完成

    下面那就运用addStatusListener()开始或结束反转动画。那就产生循环效果:

    1. //补间动画
    2. animation = new Tween(
    3. //开始的值是0
    4. begin: 0.0,
    5. //结束的值是200
    6. end : 200.0,
    7. ).animate(controller)
    8. //添加动画状态
    9. ..addStatusListener((state){
    10. //如果动画完成了
    11. if(state == AnimationStatus.completed){
    12. //开始反向这动画
    13. controller.reverse();
    14. } else if(state == AnimationStatus.dismissed){
    15. //开始向前运行着动画
    16. controller.forward();
    17. }
    18. });//添加监听器

    效果如下:

    1.3.用AnimatedBuilder重构

    上面的代码存在一个问题:更改动画需要更改显示Imagewidget,更好的解决方案是将职责分离:

    1. 显示图像
    2. 定义Animation对象
    3. 渲染过渡效果 这时候可以借助AnimatedBuilder类完成此分离。AnimatedBuilder是渲染树中的一个独立的类,与AnimatedWidget类似,AnimatedBuilder自动监听来自Animation对象的通知,并根据需要将该控件树标记为脏(dirty),因此不需要手动调用addListener()
    1. //AnimatedBuilder
    2. class GrowTransition extends StatelessWidget{
    3. final Widget child;
    4. final Animation<double> animation;
    5. GrowTransition({this.child,this.animation});
    6.  
    7. @override
    8. Widget build(BuildContext context){
    9. return new MaterialApp(
    10. theme: ThemeData(
    11. primarySwatch: Colors.red
    12. ),
    13. home: new Scaffold(
    14. appBar: new AppBar(
    15. title: Text("动画demo"),
    16. ),
    17. body:new Center(
    18. child: new AnimatedBuilder(
    19. animation: animation,
    20. builder: (BuildContext context,Widget child){
    21. return new Container(
    22. //宽和高都是根据animation的值来变化
    23. height: animation.value,
    24. width: animation.value,
    25. child: child,
    26. );
    27. },
    28. child: child,
    29. ),
    30.  
    31. ),
    32. ),
    33. );
    34. }
    35. class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{
    36. //动画的状态,如动画开启,停止,前进,后退等
    37. Animation animation;
    38. //管理者animation对象
    39. AnimationController controller;
    40. @override
    41. void initState() {
    42. // TODO: implement initState
    43. super.initState();
    44. //创建AnimationController
    45. //需要传递一个vsync参数,存在vsync时会防止屏幕外动画(
    46. //译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
    47. controller = new AnimationController(
    48. //时间是3000毫秒
    49. duration: const Duration(
    50. milliseconds:
    51. ),
    52. //vsync 在此处忽略不必要的情况
    53. vsync: this,
    54. );
    55. final CurvedAnimation curve = new CurvedAnimation(parent: controller, curve: Curves.easeIn);
    56. //补间动画
    57. animation = new Tween(
    58. //开始的值是0
    59. begin: 0.0,
    60. //结束的值是200
    61. end : 200.0,
    62. ).animate(curve)
    63. // //添加动画状态
    64. ..addStatusListener((state){
    65. //如果动画完成了
    66. if(state == AnimationStatus.completed){
    67. //开始反向这动画
    68. controller.reverse();
    69. } else if(state == AnimationStatus.dismissed){
    70. //开始向前运行着动画
    71. controller.forward();
    72. }
    73. });//添加监听器
    74. //只显示动画一次
    75. controller.forward();
    76. }
    77.  
    78. @override
    79. Widget build(BuildContext context){
    80. //return AnimatedLogo(animation: animation);
    81. return new GrowTransition(child:ImageLogo,animation: animation);
    82. }
    83.  
    84. @override
    85. void dispose() {
    86. // TODO: implement dispose
    87. super.dispose();
    88. //资源释放
    89. controller.dispose();
    90. }
    91. }

    上面代码有一个迷惑的问题是,child看起来好像是指定了两次,但实际发生的事情是,将外部引用的child传递给AnimatedBuilderAnimatedBuilder将其传递给匿名构造器,然后将该对象用作其子对象。最终的结果是AnimatedBuilder插入到渲染树中的两个Widget之间。最后,在initState()方法创建一个AnimationController和一个Tween,然后通过animate()绑定,在build方法中,返回带有一个Image为子对象的GrowTransition对象和一个用于驱动过渡的动画对象。如果只是想把可复用的动画定义成一个widget,那就用AnimatedWidget

    1.4.并行动画

    很多时候,一个动画需要两种或者两种以上的动画,在Flutter也是可以实现的,每一个Tween管理动画的一种效果,如:

    1. final AnimationController controller =
    2. new AnimationController(duration: const Duration(milliseconds: ), vsync: this);
    3. final Animation<double> sizeAnimation =
    4. new Tween(begin: 0.0, end: 300.0).animate(controller);
    5. final Animation<double> opacityAnimation =
    6. new Tween(begin: 0.1, end: 1.0).animate(controller);

    可以通过sizeAnimation.Value来获取大小,通过opacityAnimation.value来获取不透明度,但AnimatedWidget的构造函数只能接受一个动画对象,解决这个问题,需要动画的widget创建了自己的Tween对象,上代码:

    1. //AnimatedBuilder
    2. class GrowTransition extends StatelessWidget {
    3. final Widget child;
    4. final Animation<double> animation;
    5.  
    6. GrowTransition({this.child, this.animation});
    7. static final _opacityTween = new Tween<double>(begin: 0.1, end: 1.0);
    8. static final _sizeTween = new Tween<double>(begin: 0.0, end: 200.0);
    9.  
    10. @override
    11. Widget build(BuildContext context) {
    12. return new MaterialApp(
    13. theme: ThemeData(primarySwatch: Colors.red),
    14. home: new Scaffold(
    15. appBar: new AppBar(
    16. title: Text("动画demo"),
    17. ),
    18. body: new Center(
    19. child: new AnimatedBuilder(
    20. animation: animation,
    21. builder: (BuildContext context, Widget child) {
    22. return new Opacity(
    23. opacity: _opacityTween.evaluate(animation),
    24. child: new Container(
    25. //宽和高都是根据animation的值来变化
    26. height: _sizeTween.evaluate(animation),
    27. width: _sizeTween.evaluate(animation),
    28. child: child,
    29. ),
    30. );
    31. },
    32. child: child,
    33. ),
    34. ),
    35. ),
    36. );
    37. }
    38. }
    39.  
    40. class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
    41. //动画的状态,如动画开启,停止,前进,后退等
    42. Animation<double> animation;
    43.  
    44. //管理者animation对象
    45. AnimationController controller;
    46.  
    47. @override
    48. void initState() {
    49. // TODO: implement initState
    50. super.initState();
    51. //创建AnimationController
    52. //需要传递一个vsync参数,存在vsync时会防止屏幕外动画(
    53. //译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
    54. controller = new AnimationController(
    55. //时间是3000毫秒
    56. duration: const Duration(milliseconds: ),
    57. //vsync 在此处忽略不必要的情况
    58. vsync: this,
    59. );
    60. //新增
    61. animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn)
    62. ..addStatusListener((state) {
    63. //如果动画完成了
    64. if (state == AnimationStatus.completed) {
    65. //开始反向这动画
    66. controller.reverse();
    67. } else if (state == AnimationStatus.dismissed) {
    68. //开始向前运行着动画
    69. controller.forward();
    70. }
    71. }); //添加监听器
    72. //只显示动画一次
    73. controller.forward();
    74. }
    75.  
    76. @override
    77. Widget build(BuildContext context) {
    78. return new GrowTransition(child:ImageLogo,animation: animation);
    79. }
    80.  
    81. @override
    82. void dispose() {
    83. // TODO: implement dispose
    84. super.dispose();
    85. //资源释放
    86. controller.dispose();
    87. }
    88. }

    可以看到在GrowTransition定义两个Tween动画,并且加了不透明Opacitywidget,最后在initState方法中修改增加一句animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn),最后的动画效果:

    注意:可以通过改变Curves.easeIn值来实现非线性运动效果。

  • 2.自定义动画

    示例2:

    2.1.自定义小球

    1. class _bollView extends CustomPainter{
    2. //颜色
    3. Color color;
    4. //数量
    5. int count;
    6. //集合放动画
    7. List<Animation<double>> ListAnimators;
    8. _bollView({this.color,this.count,this.ListAnimators});
    9. @override
    10. void paint(Canvas canvas,Size size){
    11. //绘制流程
    12. double boll_radius = (size.width - ) / ;
    13. Paint paint = new Paint();
    14. paint.color = color;
    15. paint.style = PaintingStyle.fill;
    16. //因为这个wiaget是80 球和球之间相隔5
    17. for(int i = ; i < count;i++){
    18. double value = ListAnimators[i].value;
    19. //确定圆心 半径 画笔
    20. //第一个球 r
    21. //第二个球 5 + 3r
    22. //第三个球 15 + 5r
    23. //第四个球 30 + 7r
    24. //半径也是随着动画值改变
    25. canvas.drawCircle(new Offset((i+) * boll_radius + i * boll_radius + i * ,size.height / ), boll_radius * (value > ? ( - value) : value), paint);
    26. }
    27. }
    28.  
    29. //刷新是否重绘
    30. @override
    31. bool shouldRepaint(CustomPainter oldDelegate){
    32. return oldDelegate != this;
    33. }
    34. }

    2.2.配置小球属性

    1. class MyBalls extends StatefulWidget{
    2. Size size;
    3. Color color;
    4. int count;
    5. int seconds;
    6.  
    7. //默认四个小球 红色
    8. MyBalls({this.size,this.seconds : ,this.color :Colors.redAccent,this.count : });
    9. @override
    10. State<StatefulWidget> createState(){
    11. return MyBallsState();
    12. }
    13. }

    2.3.创建动画

    1. //继承TickerProviderStateMixin,提供Ticker对象
    2. class MyBallsState extends State<MyBalls> with TickerProviderStateMixin {
    3. //动画集合
    4. List<Animation<double>>animatios = [];
    5. //控制器集合
    6. List<AnimationController> animationControllers = [];
    7. //颜色
    8. Animation<Color> colors;
    9.  
    10. @override
    11. void initState(){
    12. super.initState();
    13. for(int i = ;i < widget.count;i++){
    14. //创建动画控制器
    15. AnimationController animationController = new AnimationController(
    16. vsync: this,
    17. duration: Duration(
    18. milliseconds: widget.count * widget.seconds
    19. ));
    20. //添加到控制器集合
    21. animationControllers.add(animationController);
    22. //颜色随机
    23. colors = ColorTween(begin: Colors.red,end:Colors.green).animate(animationController);
    24. //创建动画 每个动画都要绑定控制器
    25. Animation<double> animation = new Tween(begin: 0.1,end:1.9).animate(animationController);
    26. animatios.add(animation);
    27. }
    28. animatios[].addListener((){
    29. //刷新
    30. setState(() {
    31. });
    32. });
    33.  
    34. //延迟执行
    35. var delay = (widget.seconds ~/ ( * animatios.length - ));
    36. for(int i = ;i < animatios.length;i++){
    37. Future.delayed(Duration(milliseconds: delay * i),(){
    38. animationControllers[i]
    39. ..repeat().orCancel;
    40. });
    41. }
    42. }
    43. @override
    44. Widget build(BuildContext context){
    45. return new CustomPaint(
    46. //自定义画笔
    47. painter: _bollView(color: colors.value,count: widget.count,ListAnimators : animatios),
    48. size: widget.size,
    49. );
    50. }
    51. //释放资源
    52. @override
    53. void dispose(){
    54. super.dispose();
    55. animatios[].removeListener((){
    56. setState(() {
    57. });
    58. });
    59. animationControllers[].dispose();
    60. }
    61. }

    2.4.调用

    1. class Ball extends StatelessWidget{
    2. @override
    3. Widget build(BuildContext context){
    4. return MaterialApp(
    5. home: Scaffold(
    6. appBar: AppBar(
    7. title: Text('Animation demo'),
    8. ),
    9. body: Center(
    10. child: MyBalls(size: new Size(80.0,20.0)),
    11. ),
    12. ),
    13. );
    14. }
    15. }

四,总结

  本篇文章从简单的例子出发, 并且结合了源码, 分析了 Flutter 动画实现的原理。Flutter 以硬件设备刷新为驱动, 驱使 widget 依据给定的值生成新动画帧, 从而实现了动画效果。

链接:
  1. https://juejin.im/post/5cdbbc01f265da037b6134d9
      2.https://juejin.im/post/5c617e34f265da2d90581613

【Flutter学习】之动画实现原理浅析(三)的更多相关文章

  1. 【Flutter学习】之动画实现原理浅析(一)

    一,动画介绍 动画对于App来说,非常的重要.很多App,正是因为有了动画,所以才会觉得炫酷.移动端的动画库有非常的多,例如iOS上的Pop.web端的animate.css.Android端的And ...

  2. 【Flutter学习】之动画实现原理浅析(二)

    1. 介绍 本文会从代码层面去介绍Flutter动画,因此不会涉及到Flutter动画的具体使用. 1.1 Animation库 Flutter的animation库只依赖两个库,Dart库以及phy ...

  3. Dubbo学习(一) Dubbo原理浅析

    一.初入Dubbo Dubbo学习文档: http://dubbo.incubator.apache.org/books/dubbo-user-book/ http://dubbo.incubator ...

  4. Flutter学习笔记(36)--常用内置动画

    如需转载,请注明出处:Flutter学习笔记(36)--常用内置动画 Flutter给我们提供了很多而且很好用的内置动画,这些动画仅仅需要简单的几行代码就可以实现一些不错的效果,Flutter的动画分 ...

  5. Flutter学习笔记(37)--动画曲线Curves 效果

    如需转载,请注明出处:Flutter学习笔记(37)--动画曲线Curves 效果

  6. 【Flutter学习一】Android的App的三种开发方式

    是时候学习新技术了: 转自:https://blog.csdn.net/qq_41346910/article/details/86692124 移动开发发展到现在,已经出现了三种开发方式.本文我将为 ...

  7. 脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手

    .引言 网络编程中TCP协议的三次握手和四次挥手的问题,在面试中是最为常见的知识点之一.很多读者都知道“三次”和“四次”,但是如果问深入一点,他们往往都无法作出准确回答. 本篇文章尝试使用动画图片的方 ...

  8. [转帖]脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手

    脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手   http://www.52im.net/thread-1729-1-1.html     1.引言 网络编程中TCP协议的三次握手和 ...

  9. Android窗口管理服务WindowManagerService显示窗口动画的原理分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8611754 在前一文中,我们分析了Activi ...

随机推荐

  1. Angular JS - 2 - angularjs helloworld

    材料下载  https://github.com/liuch0228/AngularJS-learn.git 1.使用原生jquery实现 实现输入框内容 在页面上跟随输入值动态更新 项目路径 < ...

  2. JS中的作用域及闭包

    1.JS中的作用域 在 es6 出现之前JS中只有全局作用域和函数作用域,没有块级作用域,即 JS 在函数体内有自己的作用域,但是如果不是在函数体的话就全部都是全局作用域.比如在 if.for 等有 ...

  3. 【BZOJ2946&SPOJ1812】公共串(后缀自动机)

    题意:给出几个由小写字母构成的单词,求它们最长的公共子串的长度. 单词的数量<=5,单词的长度至少为1,最大为2000. 思路: #include<bits/stdc++.h> us ...

  4. C#操作xml完整类文件

    C#操作xml完整类文件 xml_oper.cs using ...System; using System.Data; using System.Web; using System.Xml; /** ...

  5. C#正则表达式将html代码中的所有img标签提取

    /// <summary> /// 取得HTML中所有图片的 URL. /// </summary> /// <param name="sHtmlText&qu ...

  6. webpack 中vue文件使用scss需要注意的地方

    需要使用npm添加node_sass和sass_loader 并且在配置文件中添加规则: { test: /\.scss$/, use: ["style-loader", &quo ...

  7. 中介者模式(Mediator Pattern)

    用于减少多个对象或类之间的通信复杂性. 此模式提供了一个中介类,它通常处理不同类之间的所有通信,并支持通过松散耦合来维护代码.中介者模式属于行为模式类别. 实现实例 在这里通过一个聊天室的示例来演示中 ...

  8. C#反射的实现

    一,什么是反射? 1,System.Reflection 命名空间中的类与 System.Type 使你能够获取有关加载的程序集和其中定义的类型的信息,如类.接口和值类型. 可以使用反射在运行时创建. ...

  9. k8s之ingress-nginx部署一直提示健康检查10254端口不通过问题就处理

    之前部署了一套k8s集群,但是到部署ingress-nginx的时候,一直提示10254端口拒绝不通:如下图. 这是因为我之前装的是docker1.17.默认的驱动是systemd.因为systemd ...

  10. js实现倒计时+div下落

    全部由js动态生成结点,body内无内容 <style> #count{ position: absolute; text-align: center; width: 240px; hei ...