本例的代碼參考這裏。
概述
動畫處理的基本原理是,對組件(widget)的某個或某組屬性設置一組連續變化的值,這些值在一定時間間隔內不斷被應用到該屬性上,使得組件的外觀看上去在進行平滑而連續的變動。
例如2秒內每隔0.1s將一個組件的x軸座標加1,那麼該組件看上去就是從左至右移動了2秒共20個單位。
處理組成部分
具體到Flutter,動畫處理主要分爲三個部分:
- 動畫控制器(AnimationController),控制整個動畫運行,包括開始結束和動畫時長等。
- 動畫抽象(Animation),描述了動畫運動的速率,例如組件是加速還是勻速,或者其它變化。
- 變動範圍(Tween),定義了動畫組件屬性值的變化範圍,例如從座標(0, 0)移動到(20, 0)
處理流程
上述三大組件,控制了整個動畫的運行。用文字描述,其流程主要包括:
- 初始化動畫控制器,設定動畫的時長,初始值等(如上例:2秒時長)
- 初始化變動範圍(如上例:Offset從[0, 0]到[20, 0])
- 初始化動畫抽象,定義它的運動速率(如上例:勻速變動)
- 將動畫描述的值,賦值到動畫組件的對應屬性上
- 開始執行動畫(調用動畫控制器的開始方法)
- 動畫執行結束
AnimationController定義
AnimationController是一個特殊的Animation對象。創建一個AnimationController時,需要傳遞一個vsync參數。設置此參數的目的,是希望屏幕每一幀畫面變化時能夠被通知到。也就是說,屏幕刷新的每一幀,AnimationController都會生成一個新的值(同樣也意味着,如果在屏幕外那麼就不被觸發)。這樣動畫組件就能夠完成一個連續平滑的動畫動作。
Tickers can be used by any object that wants to be notified whenever a frame triggers。
AnimationControler通常是在一個StatefulWidget中被聲明,並且附帶一個叫做SingleTickerProviderStateMixin的Mixin(原因就在上面說的,要設置vsync參數)。
class AnimationDemo extends StatefulWidget {
AnimationDemoState createState() => AnimationDemoState();
}
class AnimationDemoState extends State<AnimationDemo> with SingleTickerProviderStateMixin {
AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(duration: Duration(milliseconds: 2000), vsync: this);
...
}
@override
void dispose() {
controller.dispose(); // 離開時需要銷燬controller
super.dispose();
}
...
}
當Animation和Tween的設置完成後,簡單調用controller.forward()即可開始動畫。
Tween定義
Tween就是要改變的屬性值的變動範圍。它可以是任意的屬性類如Offset或者Color,最常見的是double。
...
AnimationController controller;
Tween<double> slideTween = Tween(begin: 0.0, end: 20.0);
...
Animation定義
Animation對象本身可以看做是動畫中所有變化值的一個集合。它包含了變化區間內的所有可取值,並返回給動畫組件當前的變動值。
Animation在使用中要設置的,是他的變動速率,如Curves.linear(線性變化)。
...
AnimationController controller;
Tween<double> slideTween = Tween(begin: 0.0, end: 20.0);
Animation<double> animation;
@override
void initState() {
super.initState();
...
animation = slideTween.animate(CurvedAnimation(parent: controller, curve: Curves.linear));
}
...
動畫組件定義
爲了說明簡單,在build方法中嵌套兩個Container組件,外部容器Container的paddingLeft跟隨動畫變動,達到移動內部Container的目的。
class AnimationDemoState extends State<AnimationDemo> with SingleTickerProviderStateMixin {
...
@override
Widget build(BuildContext context) {
return Container(
width: 200,
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: animation.value),
child: Container(
color: Colors.blue,
width: 80,
height: 80,
),
);
}
}
啓動動畫
在啓動動畫之前有一個Flutter的基本概念要說明。做過React的同學很清楚,要想render方法重新執行,要麼props有更新要麼state有更新。在Flutter也同樣如此,build方法同樣依賴於state的更新才能重新執行。
在AnimationController的說明中,我們知道因爲設置了vsync所以屏幕刷新的每一幀都會更新它的值。所以可以在Controller上加上一個listener,每次有update都調用一下setState,以此達到重新渲染UI的目的。
...
@override
void initState() {
...
animation.addListener(() => this.setState(() {}));
controller.repeat(); // 動畫重複執行
}
調用controller.repeat()方法,動畫會被反覆執行。如果想只執行一次,那麼可以使用controller.forward();