在任何系統(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、Curve、Controller、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來指定動畫的曲線,CurvedAnimation是Animation<double>類型,CurvedAnimation可以將AnimationController和Curve結(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ù)需要begin和end兩個參數(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);