如何实现一个简单的雨滴动画?手把手告诉你
本文由云+社区发表
目的
写了几个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刷新原理。
后面会梳理一下这类原理知识,否则还是有点担忧复杂动画按这种写法是否会卡顿。
此文已由作者授权腾讯云+社区发布
上一篇: 数据循环处理
下一篇: 好吃的清淡家常小菜有哪些
推荐阅读