Flutter | 通過一個小例子帶你認識動畫 Animation

Flutter筆記發表於2019-08-26

首先,我們知道在我們的APP中充斥著各種各樣的動畫,有的是用 GIF,有的用的 Flare,有的是用的 Lottie...。

而對於 Flutter 原生動畫來說,也是非常強大的。

下面就是一個小小的例子:

Flutter | 通過一個小例子帶你認識動畫 Animation
底部箭頭會 「向上移動並且逐漸透明,然後重複該動作」。

關於如何實現,後面再說,先來說一下 Flutter 中的動畫基礎知識。

動畫型別

首先 Flutter 中的動畫分為兩類:

  1. 補間動畫(Tween)
  2. 基於物理的動畫

其中我們常用的就是補間動畫,補間動畫的含義,引用「Flutter 中文網」的解釋:

“介於兩者之間”的簡稱。在補間動畫中,定義了開始點和結束點、時間線以及定義轉換時間和速度的曲線。然後由框架計算如何從開始點過渡到結束點。

其實動畫就是以一連串的畫面組成的,而補間動畫就是根據時間來計算如何過渡,然後給我們展示一連串的畫面。

Animation

Flutter 中的動畫系統基於 「Animation」,「Widgets」 可以直接將這些動畫合併到自己的 build 方法中來讀取它們的當前值或者監聽它們的狀態變化,或者可以將其作為的更復雜動畫的基礎傳遞給其他 widgets。

Animation 是一個抽象類,它主要的功能就是儲存動畫的狀態和值。

一般用 .addListener 方法來新增一個監聽器,在這個監聽器裡你可以獲取當前的狀態和值,但是這個監聽是隻要有動作就會回撥,如果只想要監聽當前的狀態,那麼就只需要用 .addStatusListener 方法。

「Animation」 的狀態有如下幾種:

  1. dismissed:一般情況,動畫會從這個狀態開始
  2. forward:執行時可能是這個
  3. reverse::執行時也可能是這個
  4. 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);
}
複製程式碼

解釋一下引數:

  1. value:初始化該動畫的值
  2. duration:持續時間
  3. reverseDuration:reverse 動畫持續時間
  4. debugLabel:一個字串,用於 Debug
  5. lowerBound:下界,該動畫可以獲得的最小值,以及該動畫已取消時候的值,不能為空。
  6. upperBound:上界,該動畫可以獲得的最大值,以及該動畫已完成時候的值,不能為空。
  7. animationBehavior:配置禁用動畫時[AnimationController]的行為。
  8. 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 本身只是定義瞭如何在兩個值之間插值,如果想要當前具體值,還是需要一個動畫的,這裡有兩種方法來獲得當前狀態的具體指:

  1. evaluate:這種方法適合用於已經寫好動畫,並且在該動畫執行時重新build widget 時比較有效。
  2. animate:這種方法返回一個 Animation,適用於給一個 Widget使用該 Tween 建立一個新的動畫。

實現開始時的效果

首先想一下這個效果:「向上移動並且逐漸透明,然後重複該動作」。

  1. 向上移動:我們利用 Container 的 margin 屬性
  2. 逐漸透明:套一層 Opacity,利用 opacity 屬性即可達到
  3. 重複動作:判斷動畫當前狀態,然後重新執行

瞭解需求以後,先來寫控制元件。

由於我們的動畫效果還算有一點點複雜,所以我們用 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」來入群。

推薦閱讀:

Flutter | WReorderList 一個可以指定兩個item互換位置的元件

Flutter | 一個關於背景顏色引發的打臉慘案

Flutter | 自定義一個 Stepper 步驟元件

img

相關文章