Flutter动画 -- Curve,Animation和AnimationController

Curve

 缓和曲线,即单位区间到单位区间的映射,也就是动画在某一段时间内的变化特性,缓动曲线是用来随着时间的推移调整动画的变化率,允许它们加速或者减速,而不是以恒定的速度移动.

 缓动曲线的映射当0.0必须映射到0.0上,1.0必须映射到1.0上。

 这个类是一个抽象类,下面是它的部分源码:

@immutable
abstract class Curve {
  const Curve();
  
  double transform(double t) {
    assert(t >= 0.0 && t <= 1.0);
    if (t == 0.0 || t == 1.0) {
      return t;
    }
    return transformInternal(t);
  }

  @protected
  double transformInternal(double t) {
    throw UnimplementedError();
  }
  
  Curve get flipped => FlippedCurve(this);

  @override
  String toString() {
    return '$runtimeType';
  }
}

 通过上面的源码可以发现,具体的计算方法其实是在transformInternal(double t)中,但是Curve并没有实现这个方法,因此在具体使用Curve的时候都是使用它的子类或者自己继承这个类来实现想要的效果

构造方法 const Curve();

 抽象的const构造函数,这个构造函数允许子类提供const构造函数,以便可以在const表达式中使用.

字段 flipped

 返回与此曲线相反的新曲线。这对于CurvedAnimation.reverseCurve通常是有用的。

方法 double transform(double t)

 返回曲线在点t处的值,这个方法必须首先确保下面两项成立:

  • td的值必须介于0.0和1.0之间
  • t在0.0和1.0处的映射必须分别对应于0.0和1.0.

 建议子类覆盖transformInternal方法而不是这个函数,因为上面的情况已经在transform的默认实现中处理过了,它将剩余逻辑委托给transformInternal

方法 double transformInternal(double t)

 在0.0 < t < 1.0的情况下,返回曲线在点t处的值。

 演示:将之前的一个点的运动轨迹使用Curve进行处理,效果如下:

//动画速率变化
  CurvedAnimation _curvedAnimation;
  
 @override
  void initState() {
    super.initState();
    //初始化动画控制器
    _animationController =
        AnimationController(duration: Duration(seconds: 3), vsync: this);
        
    //_animation = _tween.animate(_animationController);

    _curvedAnimation = CurvedAnimation(parent: _animationController,curve: Curves.bounceIn.flipped);

    _animation = _tween.animate(_curvedAnimation);

    _animation.addListener(() {
      _updatePage();
    });
  }

 最终的效果如下:

[外链图片转存失败(img-jQVhBYK0-1564037739637)(https://note.youdao.com/yws/public/resource/a9e390468cf5249223ed23567fcd1b5c/xmlnote/1E2D41742D5F4E768900628474C3C16A/14368 “使用Curve包装动画”)]

Animation class

 类型为T的动画,动画由一个值(类型为T)和一个状态组成,状态指示动画是在概念上从开始运行到结束还是从结束运行到开始,动画的实际值可能不会单调地变化(例如:动画使用一条反弹地曲线)。动画还允许其它对象监听其值或者状态的改变,These callbacks are called during the “animation” phase of the pipeline,就在重构widget之前。要创建一个可以向前或者向后运行的新动画,可以使用AnimationController

 源码如下:

import 'package:flutter/foundation.dart';

import 'tween.dart';

// Examples can assume:
// AnimationController _controller;

/// The status of an animation
enum AnimationStatus {
  /// The animation is stopped at the beginning
  dismissed,

  /// The animation is running from beginning to end
  forward,

  /// The animation is running backwards, from end to beginning
  reverse,

  /// The animation is stopped at the end
  completed,
}


typedef AnimationStatusListener = void Function(AnimationStatus status);

abstract class Animation extends Listenable implements ValueListenable {

  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;

  bool get isDismissed => status == AnimationStatus.dismissed;

  bool get isCompleted => status == AnimationStatus.completed;

  @optionalTypeArgs
  Animation drive(Animatable child) {
    assert(this is Animation);
    return child.animate(this as dynamic); // TODO(ianh): Clean this once https://github.com/dart-lang/sdk/issues/32120 is fixed.
  }

  @override
  String toString() {
    return '${describeIdentity(this)}(${toStringDetails()})';
  }
  
  String toStringDetails() {
    assert(status != null);
    String icon;
    switch (status) {
      case AnimationStatus.forward:
        icon = '\u25B6'; // >
        break;
      case AnimationStatus.reverse:
        icon = '\u25C0'; // <
        break;
      case AnimationStatus.completed:
        icon = '\u23ED'; // >>|
        break;
      case AnimationStatus.dismissed:
        icon = '\u23EE'; // |<<
        break;
    }
    assert(icon != null);
    return '$icon';
  }
}

属性 isCompleted -> bool

 判断是否在动画结束时停止,从源码中可以看出,当动画的状态为AnimationStatus.completed时返回true。

属性 isDismissed -> bool

 判断是否这个动画在开始位置停止。从源码可以看出,当动画状态为AnimationStatus.dismissed时返回true。

属性 AnimationStatus status

 表示动画当前的状态,返回一个美剧类型AnimationStatus中的一种状态。

属性 T value

 动画的当前值,源码中即返回T。

方法 addListener(VoidCallback listener)

 每当动画的值发生变化时调用这个监听器。添加的监听器可以通过方法removeListener移除。

方法 addStatusListener(AnimationStatusListener listener)

 每次动画状态改变的时候会调用这个监听器,可以使用removeStatusListener来移除掉添加的监听器.

方法 Animation drive (Animatable child)

 将Tween或者CurveTween链接到此动画,但是需要注意的是:此方法仅适用于Animation实例,也就是说,它可以调用AnimationController对象,以及CurvedAnimations,ProxyAnimations,ReverseAnimations,TrainHoppingAnimations等。它返回与方法(子方法)的参数U类型相同的动画,方法(子方法)的值是通过将给定的Tween应用于该动画的值而派生出来的.

 示例一: 给定一个AnimationController _controller,下面的代码创建一个Animation,当控制器从0.0到1.0时,它从左上角到右上角摇摆

Animation _alignment1 = _controller.drive(
    AlignmentTween(
        begin: Alignment.topLeft,
        end: Alignment.topRight
    ),
);

 实例二:_alignment.value可以在widgetbuild方法之后使用,例如:使用Alignment widget定位子部件,使子部件的位置从左上角移动到右上角,curve这种曲线是很常见的,例如在开始时使过渡变慢,在结束时使过渡变快,下面的代码演示了一种将前一个示例中的Tween链接到Curve(这里是Curves.easeIn)的方法,在这里,Tween是在其它地方创建的,作为一个可重用的变量,因为它的参数没有发生变化:

final Animatable _tween = AlignmentTween(begin:Alignment.topLeft,end:Alignment.topRight).chain(CurveTween(curve:Curves.easeIn));

//...

Animation _alignment2 = _controller.drive(_tween);

 实例三:下面的代码和上面的是完全相同的,并且使用下面这种方式在以内联方式创建渐变时更清晰,当渐变的值依赖于其它变量时,这可能是首选:

Animation alignment3 = _controller
    .drive(CurveTween(curve:Curves.easeIn))
    .drive(AlignmentTween(
        begin:Alignment.topLeft,
        end:Alignment.topRight,
    ));

AnimationController class

 动画控制器.使用这个类可以执行以下任务:

  • 使用forward或者reverse播放一个动画,或者stop停止一个动画
  • 将动画设置为特定的值
  • 定义动画的上界和下界值
  • 使用物理模拟创建一个动画效果

 默认情况下,AnimationController在给定的持续时间内线性生成范围从0.0到1.0的值,每当运行应用程序的设备准备显示新帧时,动画控制器就会生成一个新值(通常,这个速度约为每秒60个值)。

  一个AnimationController需要一个TickerProvider,通过构造方法中的vsync参数进行配置。TickerProvider接口描述了一个用于Ticker对象的工厂,Ticker是一个对象,它知道如何在SchedulerBinding中注册自己,并在每一帧中触发一个回调,AnimationController类使用一个Ticker来遍历它所控制的动画。

 如果一个AnimationContrioller是在State中被创建的,那么State可以使用TickerProviderStateMixinSingleTickerProviderStateMixin类来实现TickerProvider接口,TickerProviderStateMixin类总是为此而工作,在类只需要一个Ticker的情况下(例如:如果类在整个声明周期中只创建一个AnimationController),SingleTickerProviderStateMixin效率稍微会高一点。

 当一个AnimationController不再需要的时候,应该处理掉它,这可以减稍泄露的可能性。当一个AnimationControllerStatefulWidget一起使用时,通常会在State.initState方法中创建它,然后在State.dispose方法中处理掉它。

 启动动画的方法当动画成功完成时返回一个TickerFuture对象,并且不会抛出错误,如果一个动画被取消了,那么future永远不会完成,这个对象也有一个TickerFuture.orCancel属性,该属性返回一个future,当动画成功完成时,该future会完成,当动画终止时,该future会以一个错误完成.

 演示代码如下:

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

class AnimationDemo1 extends StatefulWidget {  
  @override
  _AnimationDemo1State createState() {
    return _AnimationDemo1State();
  }
}

class _AnimationDemo1State extends State
    with SingleTickerProviderStateMixin {
  //动画
  Animation _animation;

  //动画控制器
  AnimationController _animationController;

  @override
  void initState() {
    super.initState();
    _initAnimation();
  }

  //初始化动画的相关信息
  void _initAnimation() {
    _animationController = AnimationController(
      vsync: this,
      duration: Duration(seconds: 3),
    );

    _animation = _animationController
        .drive(CurveTween(curve: Curves.bounceIn))
        .drive(Tween(
            begin: Alignment.bottomCenter, end: Alignment.topCenter));

    _animation.addListener(() {
      _updatePage();
    });
  }

  //更新页面
  void _updatePage() {
    if (mounted) {
      setState(() {});
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      constraints: BoxConstraints.tightForFinite(),
      alignment: Alignment.bottomCenter,
      child: Column(
        children: [
          Container(
            constraints: BoxConstraints.expand(height: 300,width: 40),
            alignment: _animation.value,
            child: Container(
              constraints: BoxConstraints.expand(width: 30,height: 30.0),
              color: Colors.redAccent,
            ),
          ),
          RaisedButton(
            onPressed: (){
              fadeOutAndUpdateState();
            },
            child: Text(
              "点击启动动画"
            ),
          )
        ],
      ),
    );
  }

  @override
  void dispose() {
    if (_animationController != null) {
      _animationController.dispose();
    }
    super.dispose();
  }

  Future fadeOutAndUpdateState() async {
    try {
      await _animationController.forward().orCancel;
      await _animationController.reverse().orCancel;
      print("动画执行结束");
    } on TickerCanceled {
      print("捕获到TickerCanceled -- 动画被取消");
    }
  }
}

 在上面的代码中,首先通过给AnimationDemo1State with SingleTickerProviderStateMixin使当前类实现了TickerProvider接口,然后定义了AnimationAnimationController对象,接着通过使用AnimationControllerdrive方法添加了TweenCurve,最后给Animation对象添加监听以改变页面进行重新绘制。同时,将启动动画的方法放在了fadeOutAndUpdateState() async中,启动动画时使用了AnimationController.forward().orCancel的方式,用以让动画在适当的时候结束。

 程序的执行顺序如下:

  1. 点击启动动画,动画会首先正向执行,同时打印" 画执行状态改变:AnimationStatus.forward"
  2. 正向执行结束后,会首先打印"动画执行状态改变:AnimationStatus.completed",然后动画会反向执行,同时打印"动画执行状态改变:AnimationStatus.reverse"
  3. 反向执行结束后,会首先打印"动画执行状态改变:AnimationStatus.dismissed",接着会打印动画执行结束
  4. 动画执行过程中结束页面,会打印"捕获到TickerCanceled – 动画被取消"

 执行过程如下所示:

Flutter动画 -- Curve,Animation和AnimationController_第1张图片

 打印信息如下:

I/flutter ( 9327): 动画执行状态改变:AnimationStatus.forward
I/flutter ( 9327): 动画执行状态改变:AnimationStatus.completed
I/flutter ( 9327): 动画执行状态改变:AnimationStatus.reverse
I/flutter ( 9327): 动画执行状态改变:AnimationStatus.dismissed
I/flutter ( 9327): 动画执行结束
I/flutter ( 9327): 动画执行状态改变:AnimationStatus.forward
V/DartMessenger( 9327): Sending message with callback over channel 'flutter/keyevent'
V/DartMessenger( 9327): Sending message with callback over channel 'flutter/keyevent'
V/NavigationChannel( 9327): Sending message to pop route.
V/DartMessenger( 9327): Sending message with callback over channel 'flutter/navigation'
I/flutter ( 9327): 捕获到TickerCanceled -- 动画被取消

总结

 Flutter的动画逻辑相对来说还是比较清晰的,AnimationController用于对动画的控制,开始,结束,反向播放等,Curve用于描述动画的过程特性,加速,减速等,Animatable或者Tween及其子类用于包装需要的最终的结果类型,由于AnimationController一般是从0.0到1.0之间变化,因此在Tween中需要通过设置begin属性和end属性来进行映射。Animation就是最终得到的动画对象,通过将不同时刻Animationvalue值赋值给不同的对象来实现让对象动起来的效果。

你可能感兴趣的:(Flutter,安卓)