Flutter | 如何實現一個水波紋擴散效果的 Widget

Flutter筆記發表於2019-08-28

先來看圖:

Flutter | 如何實現一個水波紋擴散效果的 Widget

我們在日常使用 APP 當中,肯定會遇到這種效果,那麼這種效果是如何實現的呢?

確認需求

首先還是老套路,先確定一下需求,捋一下思路,然後才好寫程式碼:

  1. 首先要有一個圓
  2. 這個圓會邊擴散邊消失
  3. 當這個圓擴散到一定程度的時候再繪製一個圓
  4. 有限迴圈 / 無限迴圈
  5. 可以有 / 無 Child

捋好了思路,下面我們來開始實現。

首先要有一個圓

首先有一個圓,這個圓應該怎麼畫?我想到了兩種方案:

  1. CustomPaint
  2. ClipOver

這兩種方式都很簡單,所以我選擇了後者,因為後者更簡單(23333)。

程式碼我就不貼了,不過程式碼我已經提交到了 github.com/wanglu1209/…

這個圓會邊擴散邊消失

一邊擴散,一邊消失。

有沒有想起來我上一篇文章說起的箭頭小Demo? --- Flutter | 通過一個小例子帶你認識動畫 Animation

沒錯,這裡也是使用這種 evaluate 來計算大小和透明度。

程式碼如下:

Container(
  width: _radiusTween.evaluate(animation),
  height: _radiusTween.evaluate(animation),
  child: ClipOval(
    child: Opacity(
      opacity: _opacityTween.evaluate(animation),
      child: Container(
        color: color,
      ),
    ),
  ),
)
複製程式碼

這樣,我們只需要設定好該 Tween 的 begin 和 end 就能實現一邊擴散,一邊消失了。

當這個圓擴散到一定程度的時候再繪製一個圓

首先,我們都知道,在 Flutter 當中,如何把一個 widget 浮在另一個 widget 上。沒錯,用 Stack

那就要建立一個 List<Widget> 來存放我們的剛才定義好的「會擴散消失的圓」。

而且我們也知道,這個「會擴散消失的圓」需要一個 Animation,那也就是說每一個圓都需要一個AnimationAnimationController,那我們也需要建立一個 List<AnimationController> 來控制每一個「會擴散消失的圓」。

並且,在 AnimationStatus == completed 的時候,把該 圓移除,並且把該 controller dispose。

而且在該 Widget dispose 的時候,也應該把所有未清除的 controller 給清除掉。

大致程式碼如下:

int i = 0;
while (widget.cycles == null ? true : i < widget.cycles) {
  if (mounted) {
    setState(() {
      AnimationController _animationController;
      Animation<double> _animation;

      _animationController =
        AnimationController(vsync: this, duration: widget.duration);
      _animation = CurvedAnimation(
        parent: _animationController, curve: Curves.linear);

      _animationController.addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          children.removeAt(0);
          controllers.removeAt(0);
          _animationController.dispose();
        }
      });
      controllers.add(_animationController);
      _animationController.forward();

      widget.child != null
        ? children.insert(
        children.length - 1,
        AnimatedSpread(
          animation: _animation,
          radius: widget.radius,
          maxRadius: widget.maxRadius,
          color: widget.spreadColor,
        ))
        : children.add(AnimatedSpread(
          animation: _animation,
          radius: widget.radius,
          maxRadius: widget.maxRadius,
          color: widget.spreadColor,
        ));
    });
  }
  if (widget.cycles != null) i++;
  await Future.delayed(
    Duration(milliseconds: widget.duration.inMilliseconds ~/ 3));
}
複製程式碼

每一個 animation 是有 duration 的,那麼我們就可以根據該持續時間來設定什麼時候出現第二個圓,我這裡寫的是持續時間的 1/3。

這樣看起來效果是不錯的。

有限迴圈 / 無限迴圈

在剛才的程式碼裡面其實就有這一部分的邏輯:

while (widget.cycles == null ? true : i < widget.cycles) {
  // ...
}
複製程式碼

這裡主要就是控制顯示幾次,畢竟有的需求不是一直顯示波紋效果。

可以有 / 無 Child

我這裡寫的 child 預設形狀是圓形的,大小被 SizedBox 控制為 radius 的大小:

ClipOval(
  child: SizedBox(
    width: widget.radius,
    height: widget.radius,
    child: widget.child,
  ),
),
複製程式碼

如果有 child 的話如何保證 child 永遠都是在最上面?

只需要在插入圓形的時候使用 List.insert(index, element) 方法就ok了。

這樣一個有水波紋擴散效果的 Widget 就封裝完成了。

總結

這裡我使用了和上篇文章一樣的邏輯,都是使用的 AnimatedWidget

然後用 Stack 來包裝,Future.delayed 來控制下一個圓形出現的時間。

另我個人建立了一個「Flutter 交流群」,可以新增我個人微信 「17610912320」來入群。

推薦閱讀:

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

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

Dart | 你知道 sync*/async* 是怎麼用的嗎?

img

相關文章