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

原文連接:medium.com/@felixblasc…git

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

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

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

接下來首先從背景漸變開始介紹,在 Flutter 中內置的 BoxDecoration 就支持使用 LinearGradient 來實現漸變效果,代碼以下所示:bash

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

因此咱們只須要在這個基礎上去設置動畫便可,這裏直接使用 simple_animations 來實現效果, 在 simple_animations 中咱們可使用這兩個對象來實現效果:less

  • MultiTrackTween動畫處理對象,一次安排多個補間動畫的屬性
  • ControlledAnimation一個很是簡單的基於補間動畫的控件對象

關於波形的實如今後面的文章中會介紹,這裏先背景漸變的代碼:ide

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,你能夠將這段代碼做爲模板,並使用更復雜的顏色過渡對其進行擴展。學習

如今咱們有了一個漸變背景的動畫,接着能夠添加一些新的動畫效果來完善效果,以下圖所示是咱們想要實現的最終效果:動畫

實際上,這是三個波型相互重疊的效果,這裏咱們須要確保它們彼此獨立,以便於最終能夠產生波紋的疊加效果。ui

所以,咱們須要定義一個 WaveAnimation 具有如下屬性的小控件:

  • speed:控制波浪動畫的持續時間;
  • height:設置波浪做用的區域;
  • offset:x軸的偏移,以給出不一樣的波形「起始位置」;

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

首先咱們須要爲動畫設置一個 0.0 到 2 * pi 之間的值,並將該值放入到正弦函數中,接着咱們在三個位置上採樣 y 的數值大小:左側、中間和末端。這樣從左到右就覆蓋了一個 pi 大小的間隔,所以咱們始終能夠看到一個完整的正弦波的一半。

咱們爲何要採樣三個位置?由於咱們會根據這三個位置畫一段 Path,以下圖所示提供了這個可視化效果:

咱們從左上角開始(紫色),並在右上角(紅色)添加一個二次貝塞爾函數鏈接過去,而後咱們能夠經過指定一個「控制點」(綠色)來實現這個變化,最後利用了 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 番外的世界系列文章專欄

資源推薦

相關文章
相關標籤/搜索