Flutter學(xué)習(xí)筆記31-動畫簡介

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

Animation

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

可以通過Animation來監(jiān)聽動畫每一幀以及執(zhí)行狀態(tài)的變化,Animation有如下兩個方法:

  • addListener()
    它可以用于給Animation添加幀監(jiān)聽器,在每一幀都會被調(diào)用。幀監(jiān)聽器中最常見的行為是改變狀態(tài)后調(diào)用setState()來觸發(fā)UI重建。
  • addStatusListener()
    它可以給Animation添加“動畫狀態(tài)改變”監(jiān)聽器;動畫開始、結(jié)束、正向或反向(見AnimationStatus定義)時會調(diào)用狀態(tài)改變的監(jiān)聽器。
    當(dāng)動畫的狀態(tài)發(fā)生變化時,會通知所有通過 addStatusListener 添加的監(jiān)聽器。通常情況下,動畫會從dismissed狀態(tài)開始,表示它處于變化區(qū)間的開始點。
    舉例來說,從 0.0 到1.0的動畫在dismissed狀態(tài)時的值應(yīng)該是 0.0。
    動畫進行的下一狀態(tài)可能是forward(比如從 0.0 到 1.0)或者reverse(比如從 1.0 到 0.0)最終,如果動畫到達其區(qū)間的結(jié)束點(比如 1.0),則動畫會變成completed狀態(tài)。
abstract class Animation<T> extends Listenable implements ValueListenable<T> {
  const Animation();
  
  // 添加動畫監(jiān)聽器
  @override
  void addListener(VoidCallback listener);
  
  // 移除動畫監(jiān)聽器
  @override
  void removeListener(VoidCallback listener);
    
  // 添加動畫狀態(tài)監(jiān)聽器
  void addStatusListener(AnimationStatusListener listener);
    
  // 移除動畫狀態(tài)監(jiān)聽器
  void removeStatusListener(AnimationStatusListener listener);
    
  // 獲取動畫當(dāng)前狀態(tài)
  AnimationStatus get status;
    
  // 獲取動畫當(dāng)前的值
  @override
  T get value;

Curve

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

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

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

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

AnimationController

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

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

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

Ticker

當(dāng)創(chuàng)建一個AnimationController時,需要傳遞一個vsync參數(shù),它接收一個TickerProvider類型的對象,它的主要職責(zé)是創(chuàng)建Ticker,定義如下:

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

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

Tween

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

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

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

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

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

Tween.animate

要使用Tween對象,需要調(diào)用其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。

以下代碼示例構(gòu)建了一個控制器、一條曲線和一個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);
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容