Flutter學習筆記31-動畫簡介

在任何系統的UI框架中,動畫實現的原理都是相同的:在一段時間內,快速地多次改變UI外觀;由于人眼會產生視覺暫留,所以最終看到的就是一個“連續”的動畫。我們將UI的一次改變稱為一個動畫幀,對應一次屏幕刷新,而決定動畫流暢度的一個重要指標就是幀率FPS(Frame Per Second),即每秒的動畫幀數。幀率越高則動畫就會越流暢。一般情況下,對于人眼來說,動畫幀率超過16FPS,就比較流暢了,超過32FPS就會非常的細膩平滑,而超過32FPS,人眼基本上就感受不到差別了。由于動畫的每一幀都是要改變UI輸出,所以在一個時間段內連續的改變UI輸出是比較耗資源的,對設備的軟硬件系統要求都較高,所以在UI系統中,動畫的平均幀率是重要的性能指標,在Flutter中,理想情況下是可以實現60FPS的,這和原生應用能達到的幀率是基本是持平的。
為了方便開發者創建動畫,不同的UI系統對動畫都進行了一些抽象。Flutter中也對動畫進行了抽象,主要是AnimationCurveControllerTween這四個角色,它們一起配合來完成一個完整動畫。

Animation

Animation是一個抽象類,它本身和UI渲染沒有任何關系,而它主要的功能是保存動畫的插值和狀態;其中一個比較常用的Animation類是Animation<double>Animation對象是一個在一段時間內依次生成一個區間(Tween)值的類。Animation對象在整個動畫執行過程中輸出的值可以是線性的、曲線的等等,這由Curve來決定。 根據Animation對象的控制方式,動畫可以正向運行(從起始狀態開始,到終止狀態結束),也可以反向運行,甚至可以在中間切換方向。Animation還可以生成除double之外的其他類型值,如:Animation<Color>Animation<Size>。在動畫的每一幀中,都可以通過Animation對象的value屬性獲取動畫的當前狀態值。

可以通過Animation來監聽動畫每一幀以及執行狀態的變化,Animation有如下兩個方法:

  • addListener()
    它可以用于給Animation添加幀監聽器,在每一幀都會被調用。幀監聽器中最常見的行為是改變狀態后調用setState()來觸發UI重建。
  • addStatusListener()
    它可以給Animation添加“動畫狀態改變”監聽器;動畫開始、結束、正向或反向(見AnimationStatus定義)時會調用狀態改變的監聽器。
    當動畫的狀態發生變化時,會通知所有通過 addStatusListener 添加的監聽器。通常情況下,動畫會從dismissed狀態開始,表示它處于變化區間的開始點。
    舉例來說,從 0.0 到1.0的動畫在dismissed狀態時的值應該是 0.0。
    動畫進行的下一狀態可能是forward(比如從 0.0 到 1.0)或者reverse(比如從 1.0 到 0.0)最終,如果動畫到達其區間的結束點(比如 1.0),則動畫會變成completed狀態。
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;

Curve

動畫過程可以是勻速的、勻加速的或者先加速后減速等。Flutter中通過Curve(曲線)來描述動畫過程,通常把勻速動畫稱為線性的(Curves.linear),而非勻速動畫稱為非線性的。
可以通過CurvedAnimation來指定動畫的曲線,CurvedAnimationAnimation<double>類型,CurvedAnimation可以將AnimationControllerCurve結合起來,生成一個新的Animation對象:

class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> {
  CurvedAnimation({
    // 通常傳入一個AnimationController
    @required this.parent,
    // Curve類型的對象
    @required this.curve,
    this.reverseCurve,
  });
}

Curve類是一個預置的枚舉類,定義了許多常用的曲線,具體效果可查閱文檔。也可以自定義Curve,定義一個正弦曲線:

class ShakeCurve extends Curve {
  @override
  double transform(double t) {
    return math.sin(t * math.PI * 2);
  }
}

AnimationController

AnimationController用于控制動畫,它包含動畫的啟動forward()、停止stop()反向播放 reverse()等方法。AnimationController會在動畫的每一幀,就會生成一個新的值。默認情況下,AnimationController在給定的時間段內線性的生成從0.0到1.0(默認區間)的數字。 AnimationController的定義:

class AnimationController extends Animation<double>
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
  AnimationController({
    // 初始化值
    double value,
    // 動畫執行的時間
    this.duration,
    // 反向動畫執行的時間
    this.reverseDuration,
    // 最小值
    this.lowerBound = 0.0,
    // 最大值
    this.upperBound = 1.0,
    // 刷新率ticker的回調
    @required TickerProvider vsync,
  })
}

AnimationController派生自Animation<double>,因此可以在需要Animation對象的任何地方使用。 AnimationController具有控制動畫的其他方法,例如forward()方法可以啟動正向動畫,reverse()可以啟動反向動畫。在動畫開始執行后開始生成動畫幀,屏幕每刷新一次就是一個動畫幀,在動畫的每一幀,會隨著根據動畫的曲線來生成當前的動畫值(Animation.value),然后根據當前的動畫值去構建UI,當所有動畫幀依次觸發時,動畫值會依次改變,所以構建的UI也會依次變化,所以最終我們可以看到一個完成的動畫。 另外在動畫的每一幀,Animation對象會調用其幀監聽器,等動畫狀態發生改變時(如動畫結束)會調用狀態改變監聽器。duration表示動畫執行的時長,通過它我們可以控制動畫的速度。
ps: 在某些情況下,動畫值可能會超出AnimationController的[0.0,1.0]的范圍,這取決于具體的曲線。也就是說,根據選擇的曲線,CurvedAnimation的輸出可以具有比輸入更大的范圍。例如,Curves.elasticIn等彈性曲線會生成大于或小于默認范圍的值。

Ticker

當創建一個AnimationController時,需要傳遞一個vsync參數,它接收一個TickerProvider類型的對象,它的主要職責是創建Ticker,定義如下:

abstract class TickerProvider {
  // 通過一個回調創建一個Ticker
  Ticker createTicker(TickerCallback onTick);
}

Flutter應用在啟動時都會綁定一個SchedulerBinding,通過SchedulerBinding可以給每一次屏幕刷新添加回調,而Ticker就是通過SchedulerBinding來添加屏幕刷新回調,這樣一來,每次屏幕刷新都會調用TickerCallback。使用Ticker(而不是Timer)來驅動動畫會防止屏幕外動畫(動畫的UI不在當前屏幕時,如鎖屏時)消耗不必要的資源,因為Flutter中屏幕刷新時會通知到綁定的SchedulerBinding,而Ticker是受SchedulerBinding驅動的,由于鎖屏后屏幕會停止刷新,所以Ticker就不會再觸發。
通常會將SingleTickerProviderStateMixin添加到State的定義中,然后將State對象作為vsync的值。

Tween

默認情況下,AnimationController動畫生成的值所在區間是0.0到1.0
如果希望使用這個以外的值,或者其他的數據類型,就需要使用Tween
Tween的定義:

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

Tween構造函數需要beginend兩個參數。Tween的唯一職責就是定義從輸入范圍到輸出范圍的映射。
Tween繼承自Animatable<T>,而不是繼承自Animation<T>Animatable中主要定義動畫值的映射規則。
下面是ColorTween將動畫輸入范圍映射為兩種顏色值之間過渡輸出的例子:

final Tween colorTween =
    ColorTween(begin: Colors.transparent, end: Colors.black54);

Tween對象不存儲任何狀態,它提供了evaluate(Animation<double> animation)方法,它可以獲取動畫當前映射值。 Animation對象的當前值可以通過value()方法取到。evaluate函數還執行一些其它處理,例如分別確保在動畫值為0.0和1.0時返回開始和結束狀態。

Tween.animate

要使用Tween對象,需要調用其animate()方法,然后傳入一個控制器對象。代碼示例:

final AnimationController controller = AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(controller);

animate()返回的是一個Animation,而不是一個Animatable

以下代碼示例構建了一個控制器、一條曲線和一個Tween:

final AnimationController controller =  AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
final Animation curve =  
CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(curve);
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,514評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,373評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,975評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,743評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,199評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,414評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,951評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,780評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,218評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,649評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,889評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,673評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容