本文主要介紹 Flutter 動畫相關的內容,對相關的知識點進行了梳理,並從實際例子出發,進一步分析了動畫是若是實現的。markdown
一個簡單的動畫效果ide
代碼以下函數
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> animation
和 AnimationController controller
, 這兩個對象是動畫可以運行的關鍵。動畫
咱們看到在 animation 變量的 addListener() 回調方法裏面調用了 State 的重繪方法 setState() , 而在 State 的 build() 方法裏使用了 animation 對象的值做爲 weight 的寬度和高度使用。 從這裏咱們可以推測出:animation 對象經過監聽器註冊將 animation 須要更新的變更通知給監聽器, 而監聽器又調用 setStatus() 方法讓 widget 去從新繪製, 而在繪製時,widget 又將 animation 的 value 值看成新繪製圖形的參數, 經過這樣的機制不斷地重繪這個 weight 實現了動畫的效果。ui
Animation 是 Flutter 動畫庫中的核心類,它會插入指導動畫生成的值。 Animation 對象知道一個動畫當前的狀態(例如開始、 中止、 播放、 回放), 但它不知道屏幕上繪製的是什麼, 由於 Animation 對象只是提供一個值表示當前須要展現的動畫, UI 如何繪製出圖形徹底取決於 UI 自身如何在渲染和 build() 方法裏處理這個值, 固然也能夠不作處理。 Animation<double>
是一個比較經常使用的Animation類, 泛型也能夠支持其它的類型,好比: Animation<Color>
或 Animation<Size>
。 Animation 對象就是會在一段時間內依次生成一個區間之間值的類, 它的輸出能夠是線性的、曲線的、一個步進函數或者任何其餘能夠設計的映射 好比:CurvedAnimation。this
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 自己表示的就是一個 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 是怎麼樣讓這個動畫在規定時間不斷地繪製的呢?
首先看 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」 關鍵詞便可。