Flutter之通过AnimationController源码分析学习使用Animation

涉及到的类有

  • Animation
    • AnimationController
  • _ListenerMixin
    • AnimationEagerListenerMixin
    • AnimationLocalListenersMixin
    • AnimationLocalStatusListenersMixin
  • Simulation
    • SpringSimulation
    • _InterpolationSimulation
    • _RepeatingSimulation

1. 简介

界面中,当点击一个按钮,或者切换一个页面,使用动画进行过渡是再普通不过了,而今天,我们来学习一下Flutter的Animation吧!

2. AnimationController

继承关系
Animation > AnimationController

在介绍Animation之前,首先我们看一下AnimationController

// 实现了三个Mixin 分别是eager渴望监听,local本地监听,localStatus本地状态监听
class AnimationController extends Animation<double>
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {

// value就是当前动画的值
// duration就是持续的时间
// debuglabel 就是用于识别该动画的一个标签
// lowerBound 跟 upperBound就是动画的值最大跟最小值
// vsync 可以理解为提供玩这个动画的门票
  AnimationController({
    double value,
    this.duration,
    this.debugLabel,
    this.lowerBound = 0.0,
    this.upperBound = 1.0,
    @required TickerProvider vsync,
  }) : assert(lowerBound != null),
       assert(upperBound != null),
       assert(upperBound >= lowerBound),
       assert(vsync != null),
       _direction = _AnimationDirection.forward {
    _ticker = vsync.createTicker(_tick);
    _internalSetValue(value ?? lowerBound);
  }

//该构造方法没有最大最小值,所以是无限范围
  AnimationController.unbounded({
    double value = 0.0,
    this.duration,
    this.debugLabel,
    @required TickerProvider vsync,
  }) : assert(value != null),
       assert(vsync != null),
       lowerBound = double.negativeInfinity,//极小
       upperBound = double.infinity,//极大
       _direction = _AnimationDirection.forward {
    _ticker = vsync.createTicker(_tick);
    _internalSetValue(value);
  }
//最小值
  final double lowerBound;
//最大值
  final double upperBound;
//识别该动画的一个标签
  final String debugLabel;

//获取当前对象
  Animation<double> get view => this;
//持续时间
  Duration duration;

//ticket门票
  Ticker _ticker;

//这里用于重新绑定门票
  void resync(TickerProvider vsync) {
    final Ticker oldTicker = _ticker;
    _ticker = vsync.createTicker(_tick);
    _ticker.absorbTicker(oldTicker);
  }

//模拟,这里解释暂定
  Simulation _simulation;

  @override
  double get value => _value;
  double _value;
 
//这里设置值,设置值的时候会暂停动画
  set value(double newValue) {
    assert(newValue != null);
    stop();
    _internalSetValue(newValue);
    notifyListeners();
    _checkStatusChanged();
  }

//重置为最小值
  void reset() {
    value = lowerBound;
  }

//获取当前动画的速度,如果该动画不是进行中,会返回0.0
  double get velocity {
    if (!isAnimating)
      return 0.0;
    return _simulation.dx(lastElapsedDuration.inMicroseconds.toDouble() / Duration.microsecondsPerSecond);
  }
//设置新的值后重新设置状态
  void _internalSetValue(double newValue) {
    _value = newValue.clamp(lowerBound, upperBound);//判断这个值,如果小于lower返回lower,大于upper返回upper
    if (_value == lowerBound) {
      _status = AnimationStatus.dismissed;
    } else if (_value == upperBound) {
      _status = AnimationStatus.completed;
    } else {
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.forward :
        AnimationStatus.reverse;
    }
  }

//动画经过的时间,即是开始到现在,如果结束,返回null
  Duration get lastElapsedDuration => _lastElapsedDuration;
  Duration _lastElapsedDuration;

//动画是否在运动
  bool get isAnimating => _ticker != null && _ticker.isActive;

//动画的方向,前进还是后退
  _AnimationDirection _direction;

//当前动画的状态
  @override
  AnimationStatus get status => _status;
  AnimationStatus _status;

//从from开始向前运动
  TickerFuture forward({ double from }) {
    assert(() {
      if (duration == null) {//如果持续时间为null会抛异常
        throw new FlutterError(
          'AnimationController.forward() called with no default Duration.\n'
          'The "duration" property should be set, either in the constructor or later, before '
          'calling the forward() function.'
        );
      }
      return true;
    }());
//设置状态为向前运动
    _direction = _AnimationDirection.forward;
    if (from != null)
      value = from;//让值等于from
    return _animateToInternal(upperBound);//开始运动到最大值
  }

//同上,方向为返回
  TickerFuture reverse({ double from }) {
    assert(() {
      if (duration == null) {
        throw new FlutterError(
          'AnimationController.reverse() called with no default Duration.\n'
          'The "duration" property should be set, either in the constructor or later, before '
          'calling the reverse() function.'
        );
      }
      return true;
    }());
    _direction = _AnimationDirection.reverse;
    if (from != null)
      value = from;
    return _animateToInternal(lowerBound);
  }

//动画从当前值运行到target目标值
  TickerFuture animateTo(double target, { Duration duration, Curve curve = Curves.linear }) {
    _direction = _AnimationDirection.forward;
    return _animateToInternal(target, duration: duration, curve: curve);
  }
// 插值动画
  TickerFuture _animateToInternal(double target, { Duration duration, Curve curve = Curves.linear }) {
    Duration simulationDuration = duration;
    if (simulationDuration == null) {
      assert(() {
        if (this.duration == null) {
          throw new FlutterError(
            'AnimationController.animateTo() called with no explicit Duration and no default Duration.\n'
            'Either the "duration" argument to the animateTo() method should be provided, or the '
            '"duration" property should be set, either in the constructor or later, before '
            'calling the animateTo() function.'
          );
        }
        return true;
      }());
//获取范围
      final double range = upperBound - lowerBound;
//当前动画还剩多少,百分比|range.isFinite 是否有限,即最大值或最小值是无穷就返回1.0,否则true
      final double remainingFraction = range.isFinite ? (target - _value).abs() / range : 1.0;
//持续时间*百分比,等于剩下的时间,这里duration为null,所以需要计算
      simulationDuration = this.duration * remainingFraction;
    } else if (target == value) {//如果目标值等于当前值,不运动
      // Already at target, don't animate.
      simulationDuration = Duration.zero;
    }
//先停止之前的动画
    stop();
    if (simulationDuration == Duration.zero) {//如果时间为0
      if (value != target) {//当前值不等于目标值
        _value = target.clamp(lowerBound, upperBound);//判断目标值是否超过最大跟最小,然后赋值
        notifyListeners();//刷新监听
      }
//设置状态
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.completed ://完成upper
        AnimationStatus.dismissed;//取消lower
      _checkStatusChanged();//检查值改变
      return new TickerFuture.complete();//返回已经完成
    }
    assert(simulationDuration > Duration.zero);//判断时间是否大于0
    assert(!isAnimating);//判断是否不运动
    return _startSimulation(new _InterpolationSimulation(_value, target, simulationDuration, curve));//这里开始了屏幕的运动
  }
//重复动画驱动,最大值,最小值,跟持续时间,可以实现永远不会完成的动画
  TickerFuture repeat({ double min, double max, Duration period }) {
    min ??= lowerBound;
    max ??= upperBound;
    period ??= duration;
    assert(() {
      if (period == null) {
        throw new FlutterError(
          'AnimationController.repeat() called without an explicit period and with no default Duration.\n'
          'Either the "period" argument to the repeat() method should be provided, or the '
          '"duration" property should be set, either in the constructor or later, before '
          'calling the repeat() function.'
        );
      }
      return true;
    }());
    return animateWith(new _RepeatingSimulation(min, max, period));
  }

//临界阻尼动画
//动画速度默认为1.0
  TickerFuture fling({ double velocity = 1.0 }) {
//判断方向
    _direction = velocity < 0.0 ? _AnimationDirection.reverse : _AnimationDirection.forward;
//目标值,当速度小于0的时候,方向为后退,targer为lowerBound的误差值,因为速度改变了,会出现误差
    final double target = velocity < 0.0 ? lowerBound - _kFlingTolerance.distance
                                         : upperBound + _kFlingTolerance.distance;
  //弹簧模拟|_kFlingSpringDescription为一个定制好的弹簧参数
    final Simulation simulation = new SpringSimulation(_kFlingSpringDescription, value, target, velocity)
      ..tolerance = _kFlingTolerance;//这里设置误差进去
    return animateWith(simulation);
  }
//设置模拟
  TickerFuture animateWith(Simulation simulation) {
    stop();
    return _startSimulation(simulation);
  }
//开始模拟动画
  TickerFuture _startSimulation(Simulation simulation) {
    assert(simulation != null);
    assert(!isAnimating);
//初始化值
    _simulation = simulation;
    _lastElapsedDuration = Duration.zero;
    _value = simulation.x(0.0).clamp(lowerBound, upperBound);
//门票开始
    final Future<Null> result = _ticker.start();
//根据方向设置状态
    _status = (_direction == _AnimationDirection.forward) ?
      AnimationStatus.forward :
      AnimationStatus.reverse;
    _checkStatusChanged();
    return result;
  }

//停止动画
  void stop({ bool canceled = true }) {
    _simulation = null;
    _lastElapsedDuration = null;
    _ticker.stop(canceled: canceled);
  }

//销毁动画
  @override
  void dispose() {
    assert(() {
      if (_ticker == null) {
        throw new FlutterError(
          'AnimationController.dispose() called more than once.\n'
          'A given $runtimeType cannot be disposed more than once.\n'
          'The following $runtimeType object was disposed multiple times:\n'
          '  $this'
        );
      }
      return true;
    }());
//门票撕掉
    _ticker.dispose();
    _ticker = null;
    super.dispose();
  }

//最后一次的状态
  AnimationStatus _lastReportedStatus = AnimationStatus.dismissed;
//更新最后一次状态
  void _checkStatusChanged() {
    final AnimationStatus newStatus = status;
    if (_lastReportedStatus != newStatus) {
      _lastReportedStatus = newStatus;
      notifyStatusListeners(newStatus);
    }
  }

//传入经过的时间,这里未使用,功效未名
  void _tick(Duration elapsed) {
    _lastElapsedDuration = elapsed;
//返回经过多少秒
    final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
    assert(elapsedInSeconds >= 0.0);
//获取动画值
    _value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
//模拟动画是否完成
    if (_simulation.isDone(elapsedInSeconds)) {
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.completed :
        AnimationStatus.dismissed;
      stop(canceled: false);
    }
    notifyListeners();
    _checkStatusChanged();
  }

  @override
  String toStringDetails() {
    final String paused = isAnimating ? '' : '; paused';
    final String ticker = _ticker == null ? '; DISPOSED' : (_ticker.muted ? '; silenced' : '');
    final String label = debugLabel == null ? '' : '; for $debugLabel';
    final String more = '${super.toStringDetails()} ${value.toStringAsFixed(3)}';
    return '$more$paused$ticker$label';
  }
}

好了,有没有人看完这段我加了注释的AnimationController源码呢?如果看完的话,大概对Animaiton这个类已经了解了,现在让我们试着去继承Animation吧!

3. Animation

下面这段代码继承了Animation,可以发现需要重写两个监听的添加和移除,跟AnimationController有很大的出入,相同的只有 get status 跟 get value

class MyAnimationController extends Animation<double>{
  @override
  void addListener(listener) {
    // TODO: 添加监听
  }
  @override
  void addStatusListener(AnimationStatusListener listener) {
    // TODO: 添加状态监听
  }
  @override
  void removeListener(listener) {
    // TODO: 移除监听
  }
  @override
  void removeStatusListener(AnimationStatusListener listener) {
    // TODO: 移除状态监听
  }
  // TODO: 当前状态
  @override
  AnimationStatus get status => null;
  // TODO: 当前值
  @override
  double get value => null;
}

4.揭开AnimationEagerListenerMixin, AnimationLocalListenersMixin,AnimationLocalStatusListenersMixin的面纱

1.AnimationEagerListenerMixin

继承关系
_ListenerMixin > AnimationEagerListenerMixin

他实际上就是一个抽象类,在dart里面抽象类可继承可实现,看源码知道,他主要的一个方法就是dispose,用于规定释放资源的方法

///释放此对象使用的资源。 该对象不再可用
///调用此方法后
  @mustCallSuper //该注解表示一定要调用super.dispose();
  void dispose() { }

2.AnimationLocalListenersMixin

继承关系
_ListenerMixin > AnimationLocalListenersMixin

也是一个抽象类,但是该抽象类有自己的方法体,定义了一个_listeners,用于存放listener,该list是一个观察者list

abstract class AnimationLocalListenersMixin extends _ListenerMixin {

  factory AnimationLocalListenersMixin._() => null;

  final ObserverList<VoidCallback> _listeners = new ObserverList<VoidCallback>();

//添加监听到list
  void addListener(VoidCallback listener) {
    didRegisterListener();
    _listeners.add(listener);
  }

//在list中移除监听
  void removeListener(VoidCallback listener) {
    _listeners.remove(listener);
    didUnregisterListener();
  }

//通知所有的监听器
  void notifyListeners() {
    final List<VoidCallback> localListeners = new List<VoidCallback>.from(_listeners);
    for (VoidCallback listener in localListeners) {
      try {
        if (_listeners.contains(listener))
          listener();
      } catch (exception, stack) {
        FlutterError.reportError(new FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'animation library',
          context: 'while notifying listeners for $runtimeType',
          informationCollector: (StringBuffer information) {
            information.writeln('The $runtimeType notifying listeners was:');
            information.write('  $this');
          }
        ));
      }
    }
  }
}

3.AnimationLocalStatusListenersMixin

继承关系
_ListenerMixin > AnimationLocalListenersMixin

可以看出,这个抽象类跟AnimationLocalListenersMixin类似,只不过该抽象类负责跟status打交道,这里就不解析了

5. Simulation

该类主要定制动画的运行过程,可以说相当于Android中的动画插值器

abstract class Simulation {
  Simulation({ this.tolerance = Tolerance.defaultTolerance });
//当前的位置
  double x(double time);
//当前的速度
  double dx(double time);
//是否完成
  bool isDone(double time);
//用于模糊的最大值跟最小值,接近某个值后算完成
  Tolerance tolerance;
}

先来看一下_InterpolationSimulation这个类

1. _InterpolationSimulation

class _InterpolationSimulation extends Simulation {
  _InterpolationSimulation(this._begin, this._end, Duration duration, this._curve)
    : assert(_begin != null),
      assert(_end != null),
      assert(duration != null && duration.inMicroseconds > 0),
      _durationInSeconds = duration.inMicroseconds / Duration.microsecondsPerSecond;
  final double _durationInSeconds;
  final double _begin;
  final double _end;
  final Curve _curve;
  @override
  double x(double timeInSeconds) {
//这里的动画使用秒为单位,返回当前进度
    final double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0);
//如果为开始就返回开始位置
    if (t == 0.0)
      return _begin;
//如果为结束就返回结束位置
    else if (t == 1.0)
      return _end;
//返回当前位置 (开始+(结束-开始)*变化速率(进度))
    else
      return _begin + (_end - _begin) * _curve.transform(t);
  }
  @override
  double dx(double timeInSeconds) {
// 时间容差
    final double epsilon = tolerance.time;
//获取到速率,可以计算得到为x  -_-!!
    return (x(timeInSeconds + epsilon) - x(timeInSeconds - epsilon)) / (2 * epsilon);
  }
  @override//判断当前时间是否大于约定时间为完成
  bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds;
}

上面动画从源码得知,是一个匀加速的动画效果

2._RepeatingSimulation

class _RepeatingSimulation extends Simulation {
  _RepeatingSimulation(this.min, this.max, Duration period)
    : _periodInSeconds = period.inMicroseconds / Duration.microsecondsPerSecond {
    assert(_periodInSeconds > 0.0);
  }
  final double min;
  final double max;
  final double _periodInSeconds;//时间/秒
  @override
  double x(double timeInSeconds) {
    assert(timeInSeconds >= 0.0);//时间需要大于0
    final double t = (timeInSeconds / _periodInSeconds) % 1.0;
//在两个数字之间进行线性插值
    return ui.lerpDouble(min, max, t);
  }
  @override//线性运动
  double dx(double timeInSeconds) => (max - min) / _periodInSeconds;
  @override
  bool isDone(double timeInSeconds) => false;
}

未完待续!!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容