【译】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 番外的世界系列文章专栏

资源推荐

相关文章
相关标签/搜索