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