【譯】Flutter中的花式背景動畫

戀貓de小郭發表於2020-01-14

原文連結:medium.com/@felixblasc…

本文主要介紹如何使用 simple_animations 實現漂亮的動畫效果。Demo 可見 :gsy_flutter_demo

這篇文章將會介紹一個很有意思的動畫效果,它能讓 Flutter 的頁面顯得更加友好,同時本文也將展示如何使用 simple_animations 庫,在 Flutter 上輕鬆地實現如下圖所示的動畫效果。

【譯】Flutter中的花式背景動畫

動畫所需要展示的效果是:由平滑過渡的漸變背景組成,並且在文字下面會有多個波從右向左滑動。

接下來首先從背景漸變開始介紹,在 Flutter 中內建的 BoxDecoration 就支援使用 LinearGradient 來實現漸變效果,程式碼如下所示:

return Container(
  decoration: BoxDecoration(
      gradient: LinearGradient(
          begin: Alignment.topCenter,
          end: Alignment.bottomCenter,
          colors: [color1, color2])),
);
複製程式碼

所以我們只需要在這個基礎上去設定動畫即可,這裡直接使用 simple_animations 來實現效果, 在 simple_animations 中我們可以使用這兩個物件來實現效果:

  • MultiTrackTween動畫處理物件,一次安排多個補間動畫的屬性
  • ControlledAnimation一個非常簡單的基於補間動畫的控制元件物件

關於波形的實現在後面的文章中會介紹,這裡先背景漸變的程式碼:

class AnimatedBackground extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final tween = MultiTrackTween([
      Track("color1").add(Duration(seconds: 3),
          ColorTween(begin: Color(0xffD38312), end: Colors.lightBlue.shade900)),
      Track("color2").add(Duration(seconds: 3),
          ColorTween(begin: Color(0xffA83279), end: Colors.blue.shade600))
    ]);

    return ControlledAnimation(
      playback: Playback.MIRROR,
      tween: tween,
      duration: tween.duration,
      builder: (context, animation) {
        return Container(
          decoration: BoxDecoration(
              gradient: LinearGradient(
                  begin: Alignment.topCenter,
                  end: Alignment.bottomCenter,
                  colors: [animation["color1"], animation["color2"]])),
        );
      },
    );
  }
}

複製程式碼

如上程式碼所示,在這裡這裡我們僅僅定義兩個的 Track 的顏色 color1 和 color2,並將它設定到 ControlledAnimation 中,最後在 LinearGradient 使用這兩個顏色。

是不是看起來很簡單,這裡甚至都沒有看到任何 StatefulWidgetAnimationController,你可以將這段程式碼作為模板,並使用更復雜的顏色過渡對其進行擴充套件。

【譯】Flutter中的花式背景動畫

現在我們有了一個漸變背景的動畫,接著可以新增一些新的動畫效果來完善效果,如下圖所示是我們想要實現的最終效果:

【譯】Flutter中的花式背景動畫

實際上,這是三個波型相互重疊的效果,這裡我們需要確保它們彼此獨立,以便於最終可以產生波紋的疊加效果。

因此,我們需要定義一個 WaveAnimation 具備以下屬性的小控制元件:

  • speed:控制波浪動畫的持續時間;
  • height:設定波浪作用的區域;
  • offset:x軸的偏移,以給出不同的波形“起始位置”;

接下來我們先要討論一個數學的問題,要如何實現一個週期性的迴圈弧形動畫效果呢?答案只有一個:三角函式

首先我們需要為動畫設定一個 0.0 到 2 * pi 之間的值,並將該值放入到正弦函式中,接著我們在三個位置上取樣 y 的數值大小:左側、中間和末端。這樣從左到右就覆蓋了一個 pi 大小的間隔,因此我們始終可以看到一個完整的正弦波的一半。

我們為什麼要取樣三個位置?因為我們會根據這三個位置畫一段 Path,如下圖所示提供了這個視覺化效果:

【譯】Flutter中的花式背景動畫

我們從左上角開始(紫色),並在右上角(紅色)新增一個二次貝塞爾函式連線過去,然後我們可以通過指定一個“控制點”(綠色)來實現這個變化,最後利用了 Flutter 的 Canvas 路徑繪製的方法繪製出一個 Path

然後我們讓紅色的點往橙色移動,之後讓紫色的點往黃色點以後,最後我們只需要把這個 Path 路徑給連線起來就可以了。

當你只僅關注紫色、綠色和紅色點的時候,就可以看到我們的取樣後的路徑是一個正弦波的效果。

這個二次貝塞爾函式乍一看似乎有些詭異,但是它只是想畫一條從紫色到紅色的直線(藍色)。它和綠點之間的距離越長,線條就會受到某種類似重力因素的影響得到灰色的形狀,最終的結果是線逐漸彎曲。

class AnimatedWave extends StatelessWidget {
  final double height;
  final double speed;
  final double offset;

  AnimatedWave({this.height, this.speed, this.offset = 0.0});

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, constraints) {
      return Container(
        height: height,
        width: constraints.biggest.width,
        child: ControlledAnimation(
            playback: Playback.LOOP,
            duration: Duration(milliseconds: (5000 / speed).round()),
            tween: Tween(begin: 0.0, end: 2 * pi),
            builder: (context, value) {
              return CustomPaint(
                foregroundPainter: CurvePainter(value + offset),
              );
            }),
      );
    });
  }
}

class CurvePainter extends CustomPainter {
  final double value;

  CurvePainter(this.value);

  @override
  void paint(Canvas canvas, Size size) {
    final white = Paint()..color = Colors.white.withAlpha(60);
    final path = Path();

    final y1 = sin(value);
    final y2 = sin(value + pi / 2);
    final y3 = sin(value + pi);

    final startPointY = size.height * (0.5 + 0.4 * y1);
    final controlPointY = size.height * (0.5 + 0.4 * y2);
    final endPointY = size.height * (0.5 + 0.4 * y3);

    path.moveTo(size.width * 0, startPointY);
    path.quadraticBezierTo(
        size.width * 0.5, controlPointY, size.width, endPointY);
    path.lineTo(size.width, size.height);
    path.lineTo(0, size.height);
    path.close();
    canvas.drawPath(path, white);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}
複製程式碼

對於這個控制元件,我們使用了一個 LayoutBuilder 來檢查可用的寬度再進行繪製,然後使用 ControlledAnimationPlayback.LOOP 實現從 0.0 到 2 * pi 的簡單補間資料,之後可以將當前動畫值傳遞到 CustomPainterCanvas 進行動畫繪製。

最終這個 CustomPainter 可以實現我們想要的波形路徑,但需要注意的是,我們使用的波形與不透明度需要一層層減小,這樣多個波始重疊才能始終可見。

是不是用相當少的程式碼就實現了很炫酷的動畫?

最後如下程式碼所示,我們使用 Stack 將控制元件堆疊在一起。

class FancyBackgroundApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned.fill(child: AnimatedBackground()),
        onBottom(AnimatedWave(
          height: 180,
          speed: 1.0,
        )),
        onBottom(AnimatedWave(
          height: 120,
          speed: 0.9,
          offset: pi,
        )),
        onBottom(AnimatedWave(
          height: 220,
          speed: 1.2,
          offset: pi / 2,
        )),
        Positioned.fill(child: CenteredText()),
      ],
    );
  }

  onBottom(Widget child) => Positioned.fill(
        child: Align(
          alignment: Alignment.bottomCenter,
          child: child,
        ),
      );
}
複製程式碼

【譯】Flutter中的花式背景動畫

Flutter 文章彙總地址:

Flutter 完整實戰實戰系列文章專欄

Flutter 番外的世界系列文章專欄

資源推薦

【譯】Flutter中的花式背景動畫

相關文章