flutter经过widget组合的方式实现“阴晴雨雪”

开头

在flutter中,咱们能够经过 AnimationController 及各类 Animation 搭配使用的方式去实现 Widget 的动画。git

实现的方式也很是方便,经过flutter内置好的模版代码,在你建立的dart文件中输入 sta 便可建立出基本的动画模版类。github

那么,咱们能够经过这样的Widget组合方式,实现出怎样的动画呢?bash

image

接下来,咱们就以上面的动画为例子,讲一讲Widget强大的组合性!dom

Widget 组合

由简到难,咱们依次开始组合出上面的效果。ide

晴天动画是最简单的,就是一个太阳360度不停旋转的效果学习

首先,经过模版代码 sta 建立出一个 WeatherSunny 类,初始化的 controlleranimation 分别以下动画

AnimationController _controller;
  Animation _animation;

  @override
  void initState() {
    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 60),
    );
    _animation = Tween(begin: 0.0, end: 1.0).animate(_controller);
    ...
    }
复制代码

为了达到太阳不停旋转的效果,咱们须要把动画设置成循环的,因此须要监听它的状态ui

@override
  void initState() {
    ...
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reset();
        _controller.forward();
      }
    });
    _controller.forward();
    super.initState();
  }
复制代码

因为动画须要进行Widget的刷新,因此咱们一般须要进行下面的操做:this

_controller.addListener((){
      setState(() {});
    });
复制代码

可是对于复杂度不高的动画,咱们可使用 AnimatedBuilder 去下降代码行数,因此在这里上面的监听刷新就没有必要了spa

而后是将 Animation 应用在 Widget 上

@override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (ctx, child) {
        return Container(
          decoration: BoxDecoration(border: Border.all()),
          child: Transform.rotate(
            angle: pi * 2 * _animation.value * 5,
            child: child,
          ),
        );
      },
      child: Icon(
        Icons.wb_sunny,
        size: widget.sunnySize,
        color: widget.sunColor,
      ),
    );
  }
复制代码

这里的太阳其实就是flutter默认提供的Icon,咱们让它每60s旋转 360 * 5 的度数,也就是每60s 转5圈。

到这里也许有同窗会问,为何不将 Duration 设置成12s,旋转度数设置成 360 ,效果不是同样吗?

效果确实同样,不过灵活度是不同的,等你实际操做一遍就能够体会到了。

image

晴天动画很是简单,实际上就是 旋转动画 + Icon 的组合

那么阴天动画如何实现呢,应该不少同窗已经知道了,就是 晴天动画 + Stack 的组合

首先咱们将以前的 WeatherSunny 封装好,让它能够从外部传入某些参数

WeatherSunny({
    this.sunnySize = 100,
    this.sunColor = Colors.orange,
    ...
  })
复制代码

而后咱们建立一个 WeatherCloudy 去实现阴天动画,这里的阴天动画不须要额外的动画操做,因此不用将其建立成 StatefulWidget

@override
  Widget build(BuildContext context) {
    ...
    return Container(
      width: width,
      height: height,
      child: Stack(
        children: <Widget>[
          Positioned(
            left: sunOrigin.dx + cloudSize / 6,
            top: sunOrigin.dy - cloudSize / 6,
            child: WeatherSunny(
              sunnySize: sunSize,
              sunColor: sunColor,
            ),
          ),
          Positioned(
            left: cloudOrigin.dx,
            top: cloudOrigin.dy,
            child: Icon(
              Icons.cloud,
              size: cloudSize,
              color: cloudColor,
            ),
          ),
        ],
      ),
    );
  }
复制代码

上面省去了不少细节代码,能够看到阴天的动画就是经过 Stack 组合 晴天动画 与另一个 云朵Icon,只不过咱们须要计算各个对象的相对坐标

image

落雨的动画稍微要复杂一些,由于雨点的生成都是随机的,因此须要使用到 Random()

在实现以前能够先思考一下,雨点是用什么去实现的?

也许有小伙伴早就知道了,就是经过 Container 去实现的雨点

Container(
          width: randomWidth,
          height: randomHeight,
          decoration: BoxDecoration(
              borderRadius: BorderRadius.all(Radius.circular(randomWidth / 2)),
              gradient: LinearGradient(
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                  colors: [
                    Colors.white, Theme.of(context).primaryColor,
                  ])),
        )
复制代码

Container能够实现的效果很丰富,冒充雨点也是不在话下

接下来,就是如何展现出这么多的雨点。

显然,是经过 Stack + N个Position 的结合方式

咱们能够建立出随机数量的 Container 雨点展现,而后在 Position 中设置他们的随机坐标

//雨滴随机大小
      final randomWidth = Random().nextDouble() * width / 50 + 1;
      final randomHeight = Random().nextDouble() * height / 10;
      //雨滴随机坐标
      double randomL = Random().nextDouble() * width - randomWidth;
      double randomT = Random().nextDouble() * height + randomHeight;
复制代码

不过又有一个问题来了,如何实现雨滴动画无限向下移动呢?

首先确定是须要让动画无限循环的

_controller.reset();
        _controller.forward();
复制代码

让雨滴移动经过 Transform.translate 便可

Transform.translate(
              offset: Offset(
                0,
                _animation.value * widget.droppingHeight,
              ),
              child: child,
            ),
          );
复制代码

实际上的动画应该上这个样子

image

因此还剩下一个问题,如何保证雨滴不出边界?

这里就须要用到另外一个控件 ClipRect

经过 ClipRectclipper 属性,咱们能够对显示区域进行限制,接下来自定义一个 CustomClipper

class CustomRect extends CustomClipper<Rect> {
  @override
  Rect getClip(Size size) {
    Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
    return rect;
  }

  @override
  bool shouldReclip(CustomRect oldClipper) {
    return false;
  }
}
复制代码

这样,咱们就能够把显示内容限制在 rect 的范围内

大概的代码以下

Widget build(BuildContext context) {
    final children =
        getDroppingWidget(widget.droppingHeight, widget.droppingWidth, context);

    return Container(
      width: widget.droppingWidth,
      height: widget.droppingHeight,
      decoration: BoxDecoration(border: Border.all()),
      child: AnimatedBuilder(
        animation: _animation,
        builder: (ctx, child) {
          return ClipRect(
            clipper: CustomRect(),
            child: Transform.translate(
              offset: Offset(
                0,
                _animation.value * widget.droppingHeight,
              ),
              child: child,
            ),
          );
        },
        child: Stack(
          children: [
            Transform.translate(
              offset: Offset(0, -widget.droppingHeight),
              child: Stack(
                children: children,
              ),
            ),
            Stack(
              children: children,
            ),
          ],
        ),
      ),
    );
  }
复制代码

image

下雪的动画与下雨的动画是同样的,只是将实现 雨滴 的Widget替换为 飘雪 的Widget

Container(
          width: width,
          height: width,
          decoration: BoxDecoration(
              shape: BoxShape.circle,
              gradient: LinearGradient(
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                  colors: [
                    Colors.white,
                    Theme.of(context).primaryColor,
                  ])),
        );
复制代码

image

最后还有 雨雪 + 云 的动画,具体实现方式与 晴 + 云 的效果是差很少的,只是须要进行位置的计算有所不一样

那么,经过 widget 组合实现一些动画效果就到此为止,能够看到在flutter 中 万物基于widget 绝非空口无凭,

附录

demo地址以下:

【weather_animation_demo】

(ps:demo中我将控件进行了封装,能够很方便的调用,原本是打算写成一个dart package的,后来以为效果比较简单,仍是用做学习素材最为合适!

封装后,经过 droppingType 参数来控制降低的是与仍是雪,经过 droppingLevel 参数控制雨雪的数量。 也能够经过 droppingWidget 参数来自定义下落的控件。 )

相关文章
相关标签/搜索