Flutter实现雨滴动画

Flutter实现雨滴动画

目的

写了几个Flutter的demo,但是对Flutter的自定义view和动画都不太了解,看到一个类似效果在android的实现,就尝试用Flutter做一下。同时也是学习Flutter的自定义view和动画相关的知识。

效果

效果动图

在蓝色区域点击,会产品水波纹动画。

宛如水珠落在池塘,雨滴落在青青草地~

思路

动画很简单,虽然有多个雨滴,不过每次点击都是重复的动画,所以只用管一个雨滴动画是怎么实现的,其他的都是重复。

单独来看一个雨滴动画,其实就是一个圆圈慢慢的变大同时慢慢的变浅,最后消失。

所以我们封装一套上述的动画逻辑,然后在用户每次点击时生成一个相应的动画即可。

实现

自定义view

首先我们要解决的是自定义view的问题,我们知道Flutter中的一起UI皆Flutter,但是不同于android中的View会直接提供一个draw方法让你做自由的绘制操作。在Flutter中,除了StatefuleWidget等申明了支持继承的类外,其他的都是不建议继承重写的。如要要做一个新的Widget,官方建议是通过组合Widget来实现。

当然对于我们这里这种需要自己做绘制操作的,就不是组合可以解决的了,这种情况下,Flutter提供了CustomPainter类,这个类提供了paint方法,可以通过重写该方法,实现对canvas的绘制。然后作为CustomPaint的参数,控制该Widget的展示样式。

这里由于主要的绘制是水纹,要实现多个重复动画,所以具体的绘制逻辑封装了起来

class RainDrop extends CustomPainter {
  RainDrop(this.rainList);

  List<RainDropDrawer> rainList = List(); // 雨点列表
  Paint _paint = new Paint()..style = PaintingStyle.stroke; // 配置画笔

  @override
  void paint(Canvas canvas, Size size) {
    rainList.forEach((item) {
      item.drawRainDrop(canvas, _paint); // 实际的绘制逻辑
    });
    rainList.removeWhere((item) { // 移出无效对象
      return !item.isValid();
    });
  }
  // ...
}

水纹圈的绘制

每一个水纹的动画都是一样的,所以统一封装了起来。

class RainDropDrawer {
  static const double MAX_RADIUS = 30;
  double posX;
  double posY;
  double radius = 5;

  RainDropDrawer(this.posX, this.posY); // (2)

  drawRainDrop(Canvas canvas, Paint paint) { // (1)
    double opt = (MAX_RADIUS - radius) / MAX_RADIUS; // (3)
    paint.color = Color.fromRGBO(0, 0, 0, opt);
    canvas.drawCircle(Offset(posX, posY), radius, paint); // (4)
    radius += 0.5;
  }

  bool isValid() { // (5)
    return radius < MAX_RADIUS;
  }
}

注释(1)处,上文提到的CustomPainter会把canvas传过来,在这里完成单个水纹的绘制工作。

注释(2)处,每个水纹圈需要确定的是位置,只要位置就行了,大小是随着时间均匀扩大的,给默认起始值就行。

注释(3)处,透明度是随着半径扩大而逐渐透明的,这里简单的做了线性的映射。

注释(4)处,绘制水纹圈,然后让水纹半径自增,实现每次绘制扩大的效果。

注释(5)处,给定失效的条件。超过一定半径这个水纹就消失了。

扩散动画

Flutter中提供了很多的动画实现,这里用到的是AnimationController。

其实AnimationController在这里就是提供了一个回调,每次收到vsync信号时回调做一次更新。

    _animation = new AnimationController(
      // 因为是repeat的,这里的duration其实不care
        duration: const Duration(milliseconds: 200),
        vsync: this)
      ..addListener(() {
        if (_rainList.isEmpty) { //(1)
          _animation.stop();
        }
        setState(() {});
      });

这里的动画是通过repeat启动的,所以不用太关心duration,因为只要不手动关闭实际上是会一直回调的。

vsync设置的是当前的widget,提供了一个ticker,会定时回调。然后在回调中setState让当前widget更新UI。

注释(1)处是动画停止的条件判断,当每次点击往_rainList中加一个对象,每个对象绘制会判断大小是否有效,如果无效会被从列表中移出,当列表中没有元素时就停止动画。

手势识别

上述基本实现了多个雨滴的展示和动画,然后我们要来实现对用户点击的响应。

Flutter提供了GestureDetector这个widget来做手势识别。所以我们只需要用这个widget wrap住我们的自定义view,然后实现对应的手势监听方法即可。

      GestureDetector(
        onTapUp: (TapUpDetails tapUp) {
          RenderBox getBox = context.findRenderObject();
          var localOffset = getBox.globalToLocal(tapUp.globalPosition); // (1)

          var rainDrop = RainDropDrawer(localOffset.dx, localOffset.dy);
          _rainList.add(rainDrop);
          _animation.repeat(); // (2)
        },
        child: CustomPaint(
          painter: RainDrop(_rainList),
        ),
      ),

这里我们关注用户轻点后抬起的手势,这个监听的方法会传入TapUpDetails参数,这个参数含有抬起的位置参数,但是需要注意的是,这个坐标是全屏幕的坐标,而绘制的坐标是widget内的坐标,所以我们需要将这个坐标转换为我们widget内的坐标系,Flutter提供了这样的一个工具方法,参考注释(1)处的实现即可。

完成了坐标换算,就可以构建一个“雨点”对象,添加到List里面。然后在注释(2)处启动动画,就可以看到我们文章开头的动画效果啦~

总结

Flutter的动画实现起来真的很简单,提供一个差值回调,然后不停的更新即可。不过这里暂时没有考虑性能等问题,对setState这个方法感觉还是很黑盒,不太懂Flutter具体的UI刷新原理。

后面会梳理一下这类原理知识,否则还是有点担忧复杂动画按这种写法是否会卡顿。

简单封装成了一个widget,有兴趣可以看一下源码 ///////////////////////////////////////////////////////////// Wrote by Kevin(a2V2aW56aGFuMDQxN0BvdXRsb29rLmNvbQ==)

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏我有一个梦想

ClistCtrl用法及总结(由怎样隐藏ListCtrl列表头的排序小三角形这个bug学习到的知识)

1 怎样隐藏ListCtrl列表头的排序小三角形 在创建控件是加入|LVS_NOSORTHEADER风格即可。 一下是用法总结: 本文根据本人在项目中的应用,来...

34350
来自专栏技术墨客

React Forwarding高阶组件传递Refs

通常情况下,我们想获取一个组建或则一个HTML元素的实例通过 Ref特性 就可以实现,但是某些时候我们需要在子父级组建中传递使用实例,Forwarding Re...

12340
来自专栏前端知识分享

Vue---父子组件之间的通信

  在vue组件通信中其中最常见通信方式就是父子组件之中的通信,而父子组件的设定方式在不同情况下又各有不同。最常见的就是父组件为控制组件子组件为视图组件。父组件...

9920
来自专栏偏前端工程师的驿站

HTML5魔法堂:全面理解Drag & Drop API

一、前言                                      在HTML4的时代,各前端工程师为了实现拖拽功能可说是煞费苦心,初听HTML...

312100
来自专栏瓜大三哥

matlab GUI基础2

GUIDE编程开发 matlab可视化姐买你的设计,一般有两种方法,一是直接通过编辑M脚本文件产生GUI,二是通过MATLAB图形用户界面开发环境GUIDE来建...

26570
来自专栏lhyt前端之路

浏览器原理0. 前言1. 解析过程2. 渲染树2.1 CSS样式计算2.2 构建渲染树3. 布局(重要)4. 重绘与重排(重要)5. paint(绘制)6. composite(重要)7. 浏览器加载

身为前端,打交道最多的就是浏览器和node了,也是我们必须熟悉的。接下来我们讲一下浏览器工作原理和工作过程。从url到页面的过程,......,我们直接来到收到...

25920
来自专栏影子

jQuery中的常用内容总结(二)

转载请注明地址: http://www.cnblogs.com/funnyzpc/p/7571993.html

13230
来自专栏Vamei实验室

被解放的姜戈05 黑面管家

Django提供一个管理数据库的app,即django.contrib.admin。这是Django最方便的功能之一。通过该app,我们可以直接经由web页面,...

22090
来自专栏大前端_Web

Vue与React的异同-组件(二)

版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://blog.csdn.net/wkyseo/articl...

15520
来自专栏前端说吧

vue.js: 自定义事件之—— 子组件修改父组件的值

这里,相对本案例,父组件定义为Second-module,对应的子组件是Three-module

48740

扫码关注云+社区

领取腾讯云代金券