Flutter动画实现原理

先写个简单动画的例子, 代码如下:

class _AnimationSampleState extends State<AnimationSampleState>
    with SingleTickerProviderStateMixin {
  Animation<double> animation;
  AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    CurvedAnimation curve = CurvedAnimation(parent: controller, curve: Curves.easeOut);
    animation = Tween(begin: 0.0, end: 300.0).animate(curve)
      ..addListener(() {
        setState(() {
          // the state that has changed here is the animation object’s value
        });
      });
    controller.forward();
  }

  Widget build(BuildContext context) {
    return Center(
      child: Container(
        margin: EdgeInsets.symmetric(vertical: 10.0),
        height: animation.value,
        width: animation.value,
        child: FlutterLogo(),
      ),
    );
  }

  dispose() {
    controller.dispose();
    super.dispose();
  }
}

AnimationController

先看下AnimationController的声明

class AnimationController extends Animation<double>
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {

AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin都继成自_ListenerMixin.

abstract class _ListenerMixin {
  // This class is intended to be used as a mixin, and should not be
  // extended directly.
  factory _ListenerMixin._() => null;

  void didRegisterListener();
  void didUnregisterListener();
}

AnimationLocalListenersMixin代码如下,

abstract class AnimationLocalListenersMixin extends _ListenerMixin {
  factory AnimationLocalListenersMixin._() => null;

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

  void addListener(VoidCallback listener) {
    didRegisterListener();
    _listeners.add(listener);
  }

  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) {
         ... 
      }
    }
  }
}

AnimationLocalStatusListenersMixin代码如下

abstract class AnimationLocalStatusListenersMixin extends _ListenerMixin {
  factory AnimationLocalStatusListenersMixin._() => null;

  final ObserverList<AnimationStatusListener> _statusListeners = new ObserverList<AnimationStatusListener>();

  void addStatusListener(AnimationStatusListener listener) {
    didRegisterListener();
    _statusListeners.add(listener);
  }

  void removeStatusListener(AnimationStatusListener listener) {
    _statusListeners.remove(listener);
    didUnregisterListener();
  }

  void notifyStatusListeners(AnimationStatus status) {
    final List<AnimationStatusListener> localListeners = new List<AnimationStatusListener>.from(_statusListeners);
    for (AnimationStatusListener listener in localListeners) {
      try {
        if (_statusListeners.contains(listener))
          listener(status);
      } catch (exception, stack) {
         ... 
      }
    }
  }
}

这2个类就是普通的观察者模型, 实现了注册事件/移除事件/队列事件通知. 前者为value变化通知, 后者为状态变更通知.

AnimationEagerListenerMixin, 则什么也没实现. 不知道何用.

abstract class AnimationEagerListenerMixin extends _ListenerMixin {

  factory AnimationEagerListenerMixin._() => null;

  @override
  void didRegisterListener() { }

  @override
  void didUnregisterListener() { }

  @mustCallSuper
  void dispose() { }
}

Animation

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;

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

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

  @override
  String toString() {
    return '${describeIdentity(this)}(${toStringDetails()})';
  }
}

Animation主要暴露了事件注册接口, 状态以及value查询接口.

Tween

abstract class Animatable<T> {
  const Animatable();

  T evaluate(Animation<double> animation);

  Animation<T> animate(Animation<double> parent) {
    return new _AnimatedEvaluation<T>(parent, this);
  }

  Animatable<T> chain(Animatable<double> parent) {
    return new _ChainedEvaluation<T>(parent, this);
  }
}

class Tween<T extends dynamic> extends Animatable<T> {
  Tween({ this.begin, this.end });
  T begin;
  T end;

  T lerp(double t) {
    assert(begin != null);
    assert(end != null);
    return begin + (end - begin) * t;
  }

  @override
  T evaluate(Animation<double> animation) {
    final double t = animation.value;
    if (t == 0.0)
      return begin;
    if (t == 1.0)
      return end;
    return lerp(t);
  }

  @override
  bool operator ==(dynamic other) {
    if (identical(this, other))
      return true;
    if (other.runtimeType != runtimeType)
      return false;
    final Tween<T> typedOther = other;
    return begin == typedOther.begin
        && end == typedOther.end;
  }

  @override
  int get hashCode => hashValues(begin, end);

  @override
  String toString() => '$runtimeType($begin \u2192 $end)';
}

Tween继承自Animatable, 定义了begin和end2个成员. evaluate方法通过Animation的value来评估当前的值. 比如begin为100, end为200.

  • Animation.value=0.0时, evaluate的值为100.
  • Animation.value=0.1时, evaluate的值为110.
  • Animation.value=0.75时, evaluate的值为175.
  • Animation.value=1.0时, evaluate的值为200.

而使用Tween对象, 必须调用Tween的animate()方法.

  Animation<T> animate(Animation<double> parent) {
    return new _AnimatedEvaluation<T>(parent, this);
  }

内部构建了_AnimatedEvaluation对象, 传递了2个构造参数Animation<double>对象(示例代码中是CurvedAnimation)和Tween对象.

_AnimatedEvaluation代码如下

class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {
  _AnimatedEvaluation(this.parent, this._evaluatable);

  @override
  final Animation<double> parent;

  final Animatable<T> _evaluatable;

  @override
  T get value => _evaluatable.evaluate(parent);

  @override
  String toString() {
    return '$parent\u27A9$_evaluatable\u27A9$value';
  }

  @override
  String toStringDetails() {
    return '${super.toStringDetails()} $_evaluatable';
  }
}

可以得出animation.value的值来自于Tween.evaluate(CurvedAnimation.value).

CurvedAnimation

class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> {
  CurvedAnimation({
    @required this.parent,
    @required this.curve,
    this.reverseCurve
  }) : assert(parent != null),
       assert(curve != null) {
    _updateCurveDirection(parent.status);
    parent.addStatusListener(_updateCurveDirection);
  }

  @override
  final Animation<double> parent;
  Curve curve;
  Curve reverseCurve;
  AnimationStatus _curveDirection;

  void _updateCurveDirection(AnimationStatus status) {
    switch (status) {
      case AnimationStatus.dismissed:
      case AnimationStatus.completed:
        _curveDirection = null;
        break;
      case AnimationStatus.forward:
        _curveDirection ??= AnimationStatus.forward;
        break;
      case AnimationStatus.reverse:
        _curveDirection ??= AnimationStatus.reverse;
        break;
    }
  }

  bool get _useForwardCurve {
    return reverseCurve == null || (_curveDirection ?? parent.status) != AnimationStatus.reverse;
  }

  @override
  double get value {
    final Curve activeCurve = _useForwardCurve ? curve : reverseCurve;

    final double t = parent.value;
    if (activeCurve == null)
      return t;
    if (t == 0.0 || t == 1.0) {
      assert(() {
        final double transformedValue = activeCurve.transform(t);
        final double roundedTransformedValue = transformedValue.round().toDouble();
        if (roundedTransformedValue != t) {
          throw ...
          );
        }
        return true;
      }());
      return t;
    }
    return activeCurve.transform(t);
  }

  @override
  String toString() {
    ...  
  }
}

可以看出CurvedAnimation同AnimationController一样继承了Animation, 主要对value做了一个函数变换(插值器). 另外还实现了反转功能. 其构造函数需要提供一个Animation对象和Curves(插值器,函数变换)对象.

Curves内置的几种插值器



插值器类

接口代码如下:

abstract class Curve {

  const Curve();

  double transform(double t);

  Curve get flipped => new FlippedCurve(this);

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

我们自定义一个插值器, 只需要实现transform方法即可. 比如最简单的线性插值器, 直接返回t即可.

class _Linear extends Curve {
  const _Linear._();

  @override
  double transform(double t) => t;
}

总结

  • AnimationController和CurvedAnimation都继承自Animation, 可以注册事件回调.
  • CurvedAnimation是动画的插值器, 用来改变动画变化的过程.
  • AnimationController是动画的控制器, 接收时间参数, 控制动画的开始和结束.
  • Tween用于提供起始值和最终值.

这3个类的关系讲清楚了, 还漏掉了最后一点, 动画是如何被触发不断更新的.在构造AnimationController有个vsync属性.
再看下AnimationController的构造函数.

  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);
  }

vsync属性是TickerProvider对象. 例子中我们实现了SingleTickerProviderStateMixin对象.

abstract class SingleTickerProviderStateMixin<T extends StatefulWidget> extends State<T> implements TickerProvider {
  factory SingleTickerProviderStateMixin._() => null;

  Ticker _ticker;

  @override
  Ticker createTicker(TickerCallback onTick) {
    assert(() {
      if (_ticker == null)
        return true;
      throw new FlutterError(
        ...
      );
    }());
    _ticker = new Ticker(onTick, debugLabel: 'created by $this');
    return _ticker;
  }

  @override
  void dispose() {
    assert(() {
      if (_ticker == null || !_ticker.isActive)
        return true;
      throw new FlutterError(
        ...
      );
    }());
    super.dispose();
  }

  @override
  void didChangeDependencies() {
    if (_ticker != null)
      _ticker.muted = !TickerMode.of(context);
    super.didChangeDependencies();
  }
  ...
}

SingleTickerProviderStateMixin里包含了一个Ticker对象, 而Ticker对象里是由底层SchedulerBinding触发动画更新回调, 导致onTick方法被回调. 也就是AnimationController里的_tick方法. 代码如下:

  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
  double get value => _value;

这个方法会不断被底层回调, 更新value值, 检查状态变更, 然后触发回调事件.

关于value值变化过程.

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

推荐阅读更多精彩内容

  • 【Android 动画】 动画分类补间动画(Tween动画)帧动画(Frame 动画)属性动画(Property ...
    Rtia阅读 6,106评论 1 38
  • 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让全面说说Android的动画,所以今...
    未聞椛洺阅读 2,693评论 0 10
  • 本笔记的原文本链接 Property Animation Overview 属性动画总览 The property...
    Jaesoon阅读 1,090评论 2 3
  • 一: 传统 View 动画(Tween/Frame) 1.1 Tween 动画 主要有 4 中:缩放、平移、渐变、...
    dfg_fly阅读 703评论 1 2
  • 基本的动画概念和类 关键点 Animation是Flutter动画库中的核心类, 插入用于引导动画的值. Anim...
    最近不在阅读 1,668评论 2 2