Flutter動畫實現原理淺析

Flutter 動畫

本文主要介紹 Flutter 動畫相關的內容,對相關的知識點進行了梳理,並從實際例子出發,進一步分析了動畫是若是實現的。markdown

一個簡單的動畫效果ide

這是一個簡單的 Flutter Logo 動畫

代碼以下函數

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  Animation<double> animation;
  AnimationController controller;

  initState() {
    super.initState();
    controller = new AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    animation = new Tween(begin: 0.0, end: 300.0).animate(controller)
      ..addListener(() {
        setState(() {
          // Animation 對象的值改變了
        });
      });
    controller.forward();
  }
  Widget build(BuildContext context) {
  
        // ....
        height: animation.value,
        width: animation.value,
        // ....
  }
複製代碼

這部分源碼錶示的就是在 2 秒內將 logo 圖片從 0x0 繪製爲 300x300 大小的動畫。 類 _LogoAppState 是一個 StatusfulWidget的State,能夠調用 setStatus() 方法進行更新 weight。 咱們能夠看到這個例子有兩個成員變量 Animation<double> animationAnimationController controller, 這兩個對象是動畫可以運行的關鍵。動畫

咱們看到在 animation 變量的 addListener() 回調方法裏面調用了 State 的重繪方法 setState() , 而在 State 的 build() 方法裏使用了 animation 對象的值做爲 weight 的寬度和高度使用。 從這裏咱們可以推測出:animation 對象經過監聽器註冊將 animation 須要更新的變更通知給監聽器, 而監聽器又調用 setStatus() 方法讓 widget 去從新繪製, 而在繪製時,widget 又將 animation 的 value 值看成新繪製圖形的參數, 經過這樣的機制不斷地重繪這個 weight 實現了動畫的效果。ui

Animation

Animation 是 Flutter 動畫庫中的核心類,它會插入指導動畫生成的值。 Animation 對象知道一個動畫當前的狀態(例如開始、 中止、 播放、 回放), 但它不知道屏幕上繪製的是什麼, 由於 Animation 對象只是提供一個值表示當前須要展現的動畫, UI 如何繪製出圖形徹底取決於 UI 自身如何在渲染和 build() 方法裏處理這個值, 固然也能夠不作處理。 Animation<double> 是一個比較經常使用的Animation類, 泛型也能夠支持其它的類型,好比: Animation<Color>Animation<Size>。 Animation 對象就是會在一段時間內依次生成一個區間之間值的類, 它的輸出能夠是線性的、曲線的、一個步進函數或者任何其餘能夠設計的映射 好比:CurvedAnimation。this

AnimationController

AnimationController 是一個動畫控制器, 它控制動畫的播放狀態, 如例子裏面的: controller.forward() 就是控制動畫"向前"播放。 因此構建 AnimationController 對象以後動畫並無馬上開始執行。 在默認狀況下, AnimationController 會在給定的時間內線性地生成從 0.0 到 1.0 之間的數字。 AnimationController 是一種特殊的 Animation 對象了, 它父類實際上是一個 Animation<double>, 當硬件準備好須要一個新的幀的時候它就會產生一個新的值。 因爲 AnimationController 派生自 Animation <double>,所以能夠在須要 Animation 對象的任何地方使用它。 可是 AnimationController 還有其餘的方法來控制動畫的播放, 例如前面提到的 .forward() 方法啓動動畫。lua

AnimationController 生成的數字(默認是從 0.0 到 1.0) 是和屏幕刷新有關, 前面也提到它會在硬件須要一個新幀的時候產生新值。 由於屏幕通常都是 60 幀/秒, 因此它也一般一秒內生成 60 個數字。 每一個數字生成以後, 每一個 Animation 對象都會調用綁定的監聽器對象。spa

Tween

Tween 自己表示的就是一個 Animation 對象的取值範圍, 只須要設置開始和結束的邊界值(值也支持泛型)。 它惟一的工做就是定義輸入範圍到輸出範圍的映射, 輸入通常是 AnimationController 給出的值 0.0~1.0。 看下面的例子, 咱們就能知道 animation 的 value 是怎麼樣經過 AnimationController 生成的值映射到 Tween 定義的取值範圍裏面的。debug

一、 Tween.animation 經過傳入 aniamtionController 得到一個_AnimatedEvaluation 類型的 animation 對象(基類爲 Animation), 而且將 aniamtionController 和 Tween 對象傳入了 _AnimatedEvaluation 對象。設計

animation = new Tween(begin: 0.0, end: 300.0).animate(controller)
    ...
  Animation<T> animate(Animation<double> parent) {
    return _AnimatedEvaluation<T>(parent, this);
  }
複製代碼

二、 animation.value 方法便是調用 _evaluatable.evaluate(parent) 方法, 而 _evaluatable 和 parent 分別爲 Tween 對象和 AnimationController 對象。

T get value => _evaluatable.evaluate(parent);
     ....
  class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {
     _AnimatedEvaluation(this.parent, this._evaluatable);
     ....
複製代碼

三、 這裏的 animation 其實就是前面的 AnimationController 對象, transform 方法裏面的 animation.value 則就是 AnimationController 線性生成的 0.0~1.0 直接的值。 在 lerp 方法裏面咱們能夠看到這個 0.0~1.0 的值被映射到了 begin 和 end 範圍內了。

T evaluate(Animation<double> animation) => transform(animation.value);

    T transform(double t) {
    if (t == 0.0)
      return begin;
    if (t == 1.0)
      return end;
    return lerp(t);
  }

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

Flutter 的"時鐘"

那麼 Flutter 是怎麼樣讓這個動畫在規定時間不斷地繪製的呢?

首先看 Widget 引入的 SingleTickerProviderStateMixin 類。SingleTickerProviderStateMixin 是以 with 關鍵字引入的, 這是 dart 語言的 mixin 特性, 能夠理解成"繼承", 因此 widget 至關因而繼承了SingleTickerProviderStateMixin。 因此在 AnimationController 對象的構造方法參數 vsync: this, 咱們看到了這個類的使用。 從 "vsync" 參數名意爲"垂直幀同步"能夠看出, 這個是繪製動畫幀的"節奏器"。

AnimationController({
    double value,
    this.duration,
    this.debugLabel,
    this.lowerBound = 0.0,
    this.upperBound = 1.0,
    this.animationBehavior = AnimationBehavior.normal,
    @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 的構造方法中, SingleTickerProviderStateMixin 的父類 TickerProvider 會建立一個 Ticker, 並將_tick(TickerCallback 類型)回調方法綁定到了 這個 Ticker, 這樣 AnimationController 就將回調方法 _tick 和 Ticker 綁定了。

@protected
void scheduleTick({ bool rescheduling = false }) {
	assert(!scheduled);
	assert(shouldScheduleTick);
	_animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
}
複製代碼

而 Ticker 會在 start 函數內將_tick 被綁定到 SchedulerBinding 的幀回調方法內。 返回的_animationId 是 SchedulerBinding 給定的下一個動做回調的 ID, 能夠根據_animationId 來取消 SchedulerBinding 上綁定的回調。

SchedulerBinding 則是在構造方法中將本身的 _handleBeginFrame 函數和 window 的 onBeginFrame 綁定了回調。 這個回調會在屏幕須要準備顯示幀以前回調。

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

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();
  }
複製代碼

在 AnimationController 的回調當中, 會有一個 Simulation 根據動畫運行了的時間(elapsed) 來計算當前的的_value 值, 並且這個值還須要處於 Animation 設置的區間以內。 除了計算_value 值以外, 該方法還會更新 Animation Status 的狀態, 判斷是否動畫已經結束。 最後經過 notifyListeners 和_checkStatusChanged 方法通知給監聽器 value 和 AnimationStatus 的變化。 監聽 AnimationStatus 值的變化有一個專門的註冊方法 addStatusListener。

經過監聽 AnimationStatus, 在動畫開始或者結束的時候反轉動畫, 就達到了動畫循環播放的效果。

...
   animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        controller.forward();
      }
    });
    controller.forward();

    ...
複製代碼

回顧一下這個動畫繪製調用的順序就是, window 調用 SchedulerBinding 的_handleBeginFrame 方法, SchedulerBinding 調用 Ticker 的_tick 方法, Ticker 調用 AnimationController 的_tick 的方法, AnimationContoller 通知監聽器, 而監聽器調用 widget 的 setStatus 方法來調用 build 更新, 最後 build 使用了 Animation 對象當前的值來繪製動畫幀。

看到這裏會有一個疑惑, 爲何監聽器是註冊在 Animation 上的, 監聽通知反而由 AnimationController 發送?

仍是看源碼吧。

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

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

mixin AnimationWithParentMixin<T> {
  Animation<T> get parent;
  /// Listeners can be removed with [removeListener].
  void addListener(VoidCallback listener) => parent.addListener(listener);
}

複製代碼

首先 Animation 對象是由 Tween 的 animate 方法生成的, 它傳入了 AnimationController(Animation 的子類) 參數 做爲 parent 參數, 而後咱們發現返回的 _AnimatedEvaluation<T> 泛型對象 使用 mixin "繼承" 了 AnimationWithParentMixin<double>, 最後咱們看到 Animation 做爲 AnimationWithParentMixin 的"子類"實現的 addListener 方法實際上是將監聽器註冊到 parent 對象上了, 也就是 AnimationController。

總結

本篇文章從簡單的例子出發, 而且結合了源碼, 分析了 Flutter 動畫實現的原理。Flutter 以硬件設備刷新爲驅動, 驅使 widget 依據給定的值生成新動畫幀, 從而實現了動畫效果。

做者簡介

瑞恩,銅板街客戶端開發工程師,2017年11月加入團隊,目前主要負責APP端項目開發。

想要獲取更多有關 Flutter 相關內容,請掃描如下二維碼關注「銅板街科技」公衆號,並在對話框內回覆 「Flutter」 關鍵詞便可。

相關文章
相關標籤/搜索