首先,我們知道在我們的APP中充斥著各種各樣的動畫,有的是用 GIF,有的用的 Flare,有的是用的 Lottie...。
而對於 Flutter 原生動畫來說,也是非常強大的。
下面就是一個小小的例子:
關於如何實現,後面再說,先來說一下 Flutter 中的動畫基礎知識。
動畫型別
首先 Flutter 中的動畫分為兩類:
- 補間動畫(Tween)
- 基於物理的動畫
其中我們常用的就是補間動畫,補間動畫的含義,引用「Flutter 中文網」的解釋:
“介於兩者之間”的簡稱。在補間動畫中,定義了開始點和結束點、時間線以及定義轉換時間和速度的曲線。然後由框架計算如何從開始點過渡到結束點。
其實動畫就是以一連串的畫面組成的,而補間動畫就是根據時間來計算如何過渡,然後給我們展示一連串的畫面。
Animation
Flutter 中的動畫系統基於 「Animation」,「Widgets」 可以直接將這些動畫合併到自己的 build 方法中來讀取它們的當前值或者監聽它們的狀態變化,或者可以將其作為的更復雜動畫的基礎傳遞給其他 widgets。
Animation 是一個抽象類,它主要的功能就是儲存動畫的狀態和值。
一般用 .addListener
方法來新增一個監聽器,在這個監聽器裡你可以獲取當前的狀態和值,但是這個監聽是隻要有動作就會回撥,如果只想要監聽當前的狀態,那麼就只需要用 .addStatusListener
方法。
「Animation」 的狀態有如下幾種:
- dismissed:一般情況,動畫會從這個狀態開始
- forward:執行時可能是這個
- reverse::執行時也可能是這個
- completed:完成的時候會變成這個
AnimationController
要建立動畫,首先要建立一個
AnimationController
。除了Animation
本身,AnimationController
還可以用來控制動畫。例如讓動畫正向播放和停止。
那既然首先要建立一個 AnimationController
,那就看看它的建構函式,來了解一下如何建立:
AnimationController({
double value,
this.duration,
this.reverseDuration,
this.debugLabel,
this.lowerBound = 0.0,
this.upperBound = 1.0,
this.animationBehavior = AnimationBehavior.normal,
@required TickerProvider vsync,
}) : assert(lowerBound != null),
assert(upperBound != null),
assert(upperBound >= lowerBound),
assert(vsync != null),
_direction = _AnimationDirection.forward {
_ticker = vsync.createTicker(_tick);
_internalSetValue(value ?? lowerBound);
}
複製程式碼
解釋一下引數:
- value:初始化該動畫的值
- duration:持續時間
- reverseDuration:reverse 動畫持續時間
- debugLabel:一個字串,用於 Debug
- lowerBound:下界,該動畫可以獲得的最小值,以及該動畫已取消時候的值,不能為空。
- upperBound:上界,該動畫可以獲得的最大值,以及該動畫已完成時候的值,不能為空。
- animationBehavior:配置禁用動畫時[AnimationController]的行為。
- vsync:當前上下文的 TickerProvider,可以通過
resync
來更改它,不能為空。
其中 vsync 是必須的,在使用動畫的類後面加上 with TickerProviderStateMixin
就ok了。
Tween<T>
由於 AnimationController
的預設值是 0 - 1,那麼想要設定 0 - 1 以外的值就要用到 Tween,它可以設定 begin 和 end 值,其中常用的是 Tween<double>,但是也有很多特定型別的 Tween:
- ColorTween
- SizeTween
- RectTween
- CurveTween
- ...
Tween 本身只是定義瞭如何在兩個值之間插值,如果想要當前具體值,還是需要一個動畫的,這裡有兩種方法來獲得當前狀態的具體指:
- evaluate:這種方法適合用於已經寫好動畫,並且在該動畫執行時重新build widget 時比較有效。
- animate:這種方法返回一個 Animation,適用於給一個 Widget使用該 Tween 建立一個新的動畫。
實現開始時的效果
首先想一下這個效果:「向上移動並且逐漸透明,然後重複該動作」。
- 向上移動:我們利用 Container 的 margin 屬性
- 逐漸透明:套一層 Opacity,利用 opacity 屬性即可達到
- 重複動作:判斷動畫當前狀態,然後重新執行
瞭解需求以後,先來寫控制元件。
由於我們的動畫效果還算有一點點複雜,所以我們用 AnimatedWidget
來封裝該元件,避免大量重複的 setState。
所以,動畫 Widget 程式碼如下:
class AnimatedUpArrow extends AnimatedWidget {
final Tween<double> _opacityTween = Tween(begin: 1, end: 0);
final Tween<double> _marginTween = Tween(begin: 0, end: 50);
AnimatedUpArrow({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return SafeArea(
child: Center(
child: Opacity(
opacity: _opacityTween.evaluate(animation),
child: Container(
margin: EdgeInsets.only(bottom: _marginTween.evaluate(animation)),
child: Image.asset("images/UP.png", width: 20, height: 24,),
),
),
),
);
}
}
複製程式碼
首先建立兩個 Tween,由於是兩種動畫同時執行,所以這裡使用 Tween 的 evaluate 方法來通過動畫計算插值是最合適的。
程式碼很簡單,然後看一下如何使用這個 Widget:
AnimationController _animationController;
Animation<double> _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this, duration: Duration(milliseconds: 1400));
_animation =
CurvedAnimation(parent: _animationController, curve: Curves.linear);
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
Future.delayed(Duration(milliseconds: 500), () {
_animationController.reset();
});
} else if (status == AnimationStatus.dismissed) {
_animationController.forward();
}
});
_animationController.forward();
}
@override
Widget build(BuildContext context) {
return AnimatedUpArrow(
animation: _animation,
);
}
複製程式碼
簡單解釋一下:
首先定義 AnimationController
,定義了動畫持續時間為 1400,
然後定義 Animation
,設定控制器為剛才的 controller, curve 為線性,
接著定義 StatusListener
,在這裡判斷了動畫是否已經執行完成,如果已經完成,則在500毫秒的延遲之後重置動畫,並且繼續執行動畫,這樣就達到了動畫重複的效果。
最後在 build 方法中傳入設定好的 Animation
,這樣剛才定義好的 AnimatedWidget
就可以根據這個動畫來計算插值做動畫了。
這樣最上面的動畫就做好了。
總結
在 Flutter 很多原生控制元件中,都使用了 AnimatedWidget
,比如 AnimatedPositioned
,看一下它的 build 方法:
@override
Widget build(BuildContext context) {
return Positioned(
child: widget.child,
left: _left?.evaluate(animation),
top: _top?.evaluate(animation),
right: _right?.evaluate(animation),
bottom: _bottom?.evaluate(animation),
width: _width?.evaluate(animation),
height: _height?.evaluate(animation),
);
複製程式碼
可以看得出來,和我們上面用的一樣,都是 evaluate 方法。
其實在 Flutter 當中做動畫已經很方便了,起碼比 Android 舒服,2333。
程式碼已上傳至 GitHub:github.com/wanglu1209/…
另我個人建立了一個「Flutter 交流群」,可以新增我個人微信 「17610912320」來入群。
推薦閱讀: