Flutter 動畫詳解(二)

本文經過代碼層面去分析Flutter動畫的實現過程,介紹了Flutter中的Animation庫以及Physics庫。html

1. 介紹

本文會從代碼層面去介紹Flutter動畫,所以不會涉及到Flutter動畫的具體使用。git

1.1 Animation庫

Flutter的animation庫只依賴兩個庫,Dart庫以及physics庫。animation是採用Dart編寫的,因此依賴Dart庫是很正常的。physics庫是什麼呢?github

Simple one-dimensional physics simulations, such as springs, friction, and gravity, for use in user interface animations.web

physics庫是一個簡單的物理模擬的庫,包含彈簧、阻尼、重力等物理效果。前篇文章介紹過Flutter動畫,Flutter動畫兩個分類中的一個就是基於物理的動畫(Physics-based animation)。因此能夠猜想出animation庫中有一部分代碼,是實現了另外一種動畫--補間動畫(Tween Animation)。spring

經過這種庫的劃分,也能夠大體猜想出,基於物理動畫的庫是後續添加的。這說明了什麼呢?bash

  • 補間動畫是現代移動端相對基礎的動畫類型,這個是必須的;
  • 基於物理動畫是在體驗上的改善添加上去的,大體能夠猜想出爲iOS端的體驗優化;

1.2 Physics庫

Flutter基於物理的動畫,其實是至關簡單的。目前實現了彈簧、阻尼、重力三種物理效果,整個庫的代碼量也很少。詳細的代碼在下面的部分介紹,在此處,咱們先來講下基於物理的動畫庫的原理。markdown

基於物理的動畫,給咱們的感受會更真實,這是由於其更符合人們平常生活的感官。例如作一個球體下落的動畫,若是是勻速的下落,給人的感受會不夠真實,實際的生活經驗告訴咱們,球體自由下落應該是會有先慢後快的一個過程。若是讓咱們本身去實現這麼一個動畫效果,咱們會怎麼去處理呢?ide

高中物理咱們學習過自由落體相關的概念,其中的位移計算公式:函數

s = 1/2 * g * t * toop

從公式中咱們知道,自由落體的位移跟時間不是線性關係。咱們能夠根據這個公式,來實時的計算出位移來。

若是是摩擦阻尼或者彈簧呢,也都有相關的物理公式,咱們所謂的基於物理的動畫庫,也就是基於此類公式來實現的,本質上仍是補間動畫,只不過過程遵循物理規律比較複雜罷了。

2. Animation庫

講解這一部分,也考慮過將Flutter的動畫原理先介紹一下。想了想,前一篇文章介紹過這些普世的動畫原理,Flutter只不過是特定平臺的實現,無非是實現手段的不一樣,所以,Flutter動畫原理的解析,放到本文最後的小節部分,在代碼的基礎上去解釋,筆者以爲更加好理解。

2.1 animation.dart

animation.dart定義了動畫的四種狀態,以及核心的抽象類Animation。

2.1.1 動畫的四種狀態

這個文件中定義了Animation的四種狀態:

  • dismissed:動畫的初始狀態
  • forward:從頭至尾播放動畫
  • reverse:從尾到頭播放動畫
  • completed:動畫完成的狀態

2.1.2 Animation類

Animation類是Flutter動畫中核心的抽象類,它包含動畫的當前值和狀態兩個屬性。定義了動畫的一系列回調,

  • 動畫過程當中值變化的回調:
void addListener(VoidCallback listener);
void removeListener(VoidCallback listener);
複製代碼
  • 狀態的回調函數:
void addStatusListener(AnimationStatusListener listener);
void removeStatusListener(AnimationStatusListener listener);
複製代碼

2.2 curve.dart

A curve must map t=0.0 to 0.0 and t=1.0 to 1.0.

看到這段英文,首先會想到什麼?沒錯,插值器。Curve也是一個抽象類,定義了時間與數值的一個接口。

double transform(double t);
複製代碼

例如一個線性的插值器,實現代碼以下。

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

  @override
  double transform(double t) => t;
}
複製代碼

該文件下面定義了很是多類型的插值器,具體的實現不一一展開了。Flutter定義了一系列的插值器,封裝在Curves類中,有下面13種效果。

  • linear
  • decelerate
  • ease
  • easeIn
  • easeOut
  • easeInOut
  • fastOutSlowIn
  • bounceIn
  • bounceOut
  • bounceInOut
  • elasticIn
  • elasticOut
  • elasticInOut

若是上面的13種還不知足需求的話,還可使用Cubic類來進行自定義的構造。能夠看出這塊兒實現參考了web中的相關實現。

2.3 tween.dart

該文件定義了一系列的估值器,Flutter經過抽象類Animatable來實現估值器。關於Animatable,咱們能夠先看下其定義。

An object that can produce a value of type T given an [Animation] as input.

能夠根據不一樣的輸入,產出不一樣的數值。經過重載下面的函數來產生不一樣的估值器。

T transform(double t);
複製代碼

它的最主要的子類是Tween,一個線性的估值器,實現以下,很是的簡單,就是一個線性函數。

T lerp(double t) {
  assert(begin != null);
  assert(end != null);
  return begin + (end - begin) * t;
}
  
@override
T transform(double t) {
  if (t == 0.0)
    return begin;
  if (t == 1.0)
    return end;
  return lerp(t);
}
複製代碼

在Tween的基礎上實現了不一樣類型的估值器。

  • ReverseTween
  • ColorTween
  • SizeTween
  • RectTween
  • IntTween
  • StepTween
  • ConstantTween

還能夠經過自定義的插值器去實現估值器,例如經過curve實現的估值器CurveTween。

2.4 animation_controller.dart

動畫的控制,就在這個文件下面實現,其中最重要的部分是AnimationController,它派生自Animation類。

AnimationController的功能有以下幾種:

  • 播放一個動畫(forwaed或者reverse),或者中止一個動畫;
  • 設置動畫的值;
  • 設置動畫的邊界值;
  • 建立基於物理的動畫效果。

默認狀況下,AnimationController是線性的產生0.0到1.0之間的值,每刷新一幀就產出一個數值。AnimationController在不使用的時候須要dispose,不然會形成資源的泄漏。

2.4.1 TickerProvider

提到AnimationController必需要先說一下TickerProvider。

An interface implemented by classes that can vend Ticker objects.

TickerProvider定義了能夠發送Ticker對象的接口,

Ticker createTicker(TickerCallback onTick);
複製代碼

Ticker能幹什麼呢?

Tickers can be used by any object that wants to be notified whenever a frame triggers.

它的主要做用是獲取每一幀刷新的通知,做用就顯而易見了,至關於給動畫添加了一個動起來的引擎。

2.4.2 AnimationController

如今再次回到AnimationController。上面爲何要先說一下TickerProvider呢,這是由於AnimationController的構造函數中須要一個TickerProvider參數。

結合上面介紹的插值器、估值器以及Ticker回調,AnimationController大體的工做流程,我相信不少人均可以理出來了。

隨着時間的流逝,插值器根據時間產生的值做爲輸入,提供給估值器,產生動畫的實際效果值,結合Ticker的回調,渲染出當前動畫值的圖像。這也是補間動畫的工做原理。

補間動畫

AnimationController具體的源碼不作分析了,能夠看到Flutter的動畫實現的實際上是至關的原始,AnimationController須要一個觸發刷新的回調,輸出也是值的改變,並不像成熟平臺裏面的配合View去作動畫。

3. Physics庫

Physics庫基本上就是插值器的實現部分,這部分比較簡單

Physics動畫庫

Simulation定義了基於物理動畫的相關接口,具體有位置、速度、是否完成以及公差(Tolerance)

double x(double time);
double dx(double time);
複製代碼

GravitySimulation的實現以下,其中_a加速度,_x是初始距離,_v是初始速度:

@override
double x(double time) => _x + _v * time + 0.5 * _a * time * time;

@override
double dx(double time) => _v + time * _a;
複製代碼

相信學太高中物理的讀者,對這公式不會陌生。其餘幾種具體實現不在此處一一展開了哈。若是擴展這個物理動畫庫的話,也很好去擴展,掌握一些物理公式,就能夠去仿照實現了。

4. Ticker

關於動畫的驅動,在此簡單的說一下,Ticker是被SchedulerBinding所驅動。SchedulerBinding則是監聽着Window.onBeginFrame回調。

Window.onBeginFrame的做用是什麼呢,是告訴應用該提供一個scene了,它是被硬件的VSync信號所驅動的。

具體能夠查看sky_engine下面的window.dart的實現,不作展開了。

5. 小節

本篇文章簡單的從代碼的層面解析了一下Flutter的動畫,更深刻的Ticker這塊兒,感興趣的讀者能夠自行去了解,這塊兒涉及到sky_engine下面的代碼。

本篇文章,筆者依然試圖繞過代碼去講解一些普適性的東西,可是Flutter這塊兒代碼實現的確實挺簡單的,形成的問題是調用起來費勁。

基於物理的動畫,咱們要知道深層次的是物理公式,有這個基礎,咱們才能夠製做出符合感官的動畫效果。其本質也是補間動畫,過程能夠被計算出來。

能夠說的寬泛一些,通常的動畫,大部分都是補間動畫,若是咱們自行去設計一套動畫系統,插值器、估值器、驅動部分以及動畫的管理部分,這四個模塊之間相互協調輸出一幀一幀的動畫過場。絕大部分平臺的動畫設計,也都逃不過這些因素,只不過實現的方式各不相同。

若是文中有錯誤的地方,煩請指正,筆者水平有限,再次感謝。

6. 後話

筆者建了一個Flutter學習相關的項目,Github地址,裏面包含了筆者寫的關於Flutter學習相關的一些文章,會按期更新,也會上傳一些學習Demo,歡迎你們關注。

7. 參考

  1. Animations in Flutter
  2. Tutorial: Animations in Flutter
  3. TickerProvider class
  4. Ticker class
  5. SchedulerBinding class
  6. onBeginFrame property
相關文章
相關標籤/搜索