本文参考学习 视频教程-《Android 粒子效果之雨》

效果图:

本文在《【Android效果集】弹幕效果 》基础上实现,建议先阅读完再看本文。

跟着上一篇介绍弹幕效果的文章相比,这一篇其实和上一篇很类似,虽然效果看起来大相径庭,看下实现就会发现很相似,可以学会来然后举一反三做出很多好玩的动画效果!~

我们首先来分析一下每个雨点效果,每个雨点其实就是一条倾斜直线,从屏幕上/左方出来,到屏幕下/右方消失,期间沿着直线的方向移动。

不是很像弹幕吗?弹幕是从右边出来,水平移动到左边出去。


实现思路

1.在上一篇弹幕项目基础上,重构出一个BaseView,自定义RainView代表一个雨滴,继承自BaseView
2.RainView能够自动从最上面移动到最下边,且有一定倾斜角度,移动过程中一直保持着同一个倾斜角度
3.重构提取出单个雨滴类,将RainView改为包含多个雨滴代表一场雨
4.随机定制化,比如倾斜角度,颜色等


详细过程

1.重构出一个BaseView

先上代码,待会解释为什么要重构。

/*** Created by AZZ on 15/10/20 21:20.*/
public abstract class BaseView extends View {protected AnimThread animThread;protected int windowWidth; //屏幕宽protected int windowHeight; //屏幕高public BaseView(Context context) {super(context);init();}public BaseView(Context context, AttributeSet attrs) {super(context, attrs);init();}/*** 初始化*/protected void init() {Rect rect = new Rect();getWindowVisibleDisplayFrame(rect);windowWidth = rect.width();windowHeight = rect.height();}//----------------------------------------------------    画布操作/*** 画子类*/protected abstract void drawSub(Canvas canvas);@Overrideprotected void onDraw(Canvas canvas) {drawSub(canvas);if (animThread == null) {animThread = new AnimThread();animThread.start();}}//----------------------------------------------------    动画操作/*** 动画逻辑处理*/protected abstract void animLogic();/*** 里面根据当前状态判断是否需要返回停止动画* @return 是否需要停止动画thread*/protected abstract boolean needStopAnimThread();/*** @return 线程睡眠时间,值越大,动画越慢,值越小,动画越快*/protected int sleepTime() {return 30;}/*** 动画结束后做的操作,比如回收资源*/protected abstract void onAnimEnd();class AnimThread extends Thread {@Overridepublic void run() {while(true) {//1.动画逻辑animLogic();//2.绘制图像postInvalidate();//3.延迟,不然会造成执行太快动画一闪而过try {Thread.sleep(sleepTime());} catch (InterruptedException e) {e.printStackTrace();}//关闭线程逻辑判断if (needStopAnimThread()) {Log.i("BaseView", "   -线程停止!");if (mOnAnimEndListener != null) {mOnAnimEndListener.onAnimEnd();}onAnimEnd();break;}}}}//----------------------------------------------------    外部监听器,监听动画结束private OnAnimEndListener mOnAnimEndListener;/*** @param onAnimEndListener 设置滚动结束监听器*/public void setOnRollEndListener(OnAnimEndListener onAnimEndListener) {this.mOnAnimEndListener = onAnimEndListener;}/*** 滚动结束接听器*/interface OnAnimEndListener {void onAnimEnd();}
}

可以看到我在BaseView中提取出了init()——初始化,drawSub()——画布操作,animLogic()——动画操作和动画结束监听器。

为什么要这么做呢?我刚刚也讨论过了,下雨效果和弹幕效果实现十分相似,可以说它们的实现代码有很多重合的地方,而这些重复的地方正是上面BaseView里面的代码。我们写代码遇到有大量重复代码的时候怎么办?提取抽象类!正是基于此点考虑才重构的。

2.自定义RainView

我们新建RainView继承BaseView,除了两个构造方法,我们要继承实现的有5个方法:
init() - 在这里面做初始化操作,因为在BaseView中的init()以及获取了屏幕宽高,所以在子类中可以直接使用windowWidthwidthHeight

drawSub() - 在这里面绘制子类图像,待会我们就要这里面绘制雨点那条线。

animLogic() - 看BaseView中知道这个方法是每30ms调用一次,调用完该方法后就会重绘,也就是重新调用drawSub()方法,所以我们需要在animLogic()做一些参数修改,比如坐标的变化。

needStopAnimThread() - 在这个方法做一些边界判断,以及根据判断结果来选择是否要返回true来停止线程动画。

onAnimEnd() - 当needStopAnimThread()返回true时,可以在这个方法中做些操作,比如在弹幕效果中,动画结束时把BarrageView从父控件中移除达到回收资源的效果。

sleepTime() - 这个是可选实现,默认父类中返回30ms,子类中可重写,以达到改变动画执行速率的效果。

public class RainView extends BaseView {public RainView(Context context) {super(context);}public RainView(Context context, AttributeSet attrs) {super(context, attrs);}/*** 初始化*/@Overrideprotected void init() {super.init();}/*** 画子类* @param canvas*/@Overrideprotected void drawSub(Canvas canvas) {}/*** 动画逻辑处理*/@Overrideprotected void animLogic() {}/*** 里面根据当前状态判断是否需要返回停止动画** @return 是否需要停止动画thread*/@Overrideprotected boolean needStopAnimThread() {return false;}/*** 动画结束后做的操作,比如回收资源*/@Overrideprotected void onAnimEnd() {}
}

3.绘制第一条雨点

首先我们需要画条线,一条线有两个坐标点(两点确定一条直线),(startX,startY)->(stopX,stopY),当stopX > startX并且stopY > startY的时候,就画出了一条倾斜的直线。

而如果我们想让一条倾斜的直线倾斜着移动,怎么做?难道还要算角度和比例吗?

其实很简单,只需要让“这条线”上的两个坐标点的x坐标加上一个deltaX (deltaX = stopX - startX),让两个坐标点的y坐标加上一个deltaY (deltaY = stopY - startY)

public class RainView extends BaseView {private int startX;private int startY;private int stopX;private int stopY;private int deltaX = 20;private int deltaY = 30;private Paint paint;@Overrideprotected void init() {startX = 0;startY = 30;stopX = startX + deltaX;stopY = startY + deltaY;paint = new Paint();if (paint !=null) {paint.setColor(0xffffffff); //白色}}@Overrideprotected void drawSub(Canvas canvas) {canvas.drawLine(startX, startY, stopX, stopY, paint);   }@Overrideprotected void animLogic() {startX += deltaX;stopX += deltaX;startY += deltaY;stopY += deltaY;}}

这个时候运行,以及能看到一条雨点滴落啦!~(对了前提别忘了把自定义View加到主布局里去)

4.提取出单个雨点的相关属性,重构RainLine类表示单个雨点

为什么要重构出一个RainLine类呢?为什么不和之前弹幕一样,一个BarrageView就是一条弹幕呢?

这是因为在下雨的场景中,雨点的数量是非常庞大的,从几百到几千都是可能的,而我们在RainView里面是用线程刷新重绘来实现动画的,当同时有几百个RainView在一个场景下时,也就是说系统同时运行着几百个线程,这是非常可怕的。事实上开始时我确实是这么做的,实验后发现线程数超过100程序就卡的不行了。

我们提取出专门的一个雨点类,然后在RainView中的一个线程里重绘几千个雨点都是没有问题的。

public class RainLine {private Random random = new Random();private int startX;private int startY;private int stopX;private int stopY;private int deltaX = 20;private int deltaY = 30;private int maxX; //x最大范围private int maxY; //y最大范围public RainLine(int maxX, int maxY) {this.maxX = maxX;this.maxY = maxY;initRandom();}public void initRandom() {startX = random.nextInt(maxX);startY = random.nextInt(maxY);stopX = startX + deltaX;stopY = startY + deltaY;}/*** 随机初始化*/public void resetRandom() {if (random.nextBoolean()) { //随机 true, 雨点从x轴出来startY = 0;startX = random.nextInt(maxX);} else { //随机 false,雨点从y轴出来startX = 0;startY = random.nextInt(maxY);}stopX = startX + deltaX;stopY = startY + deltaY;}/*** 下雨*/public void rain() {startX += deltaX;stopX += deltaX;startY += deltaY;stopY += deltaY;}/*** @return 是否出界*/public boolean outOfBounds() {if (getStartY() >= maxY || getStartX() >= maxX) {resetRandom();return true;}return false;}
}

除了重构,我还加了几个方法,

首先,构造方法传入了maxXmaxY,也就是屏幕宽高,为后面越界处理做准备。

initRandom() - 初始化的时候雨点随机在屏幕的各个地方。

rain() - 下雨方法也就是把在RainViewanimLogic()里面的操作提取出来。

outOfBounds() - 用于判断雨点是否超过界限,超过界限有两种,最右边和最下边都算越界,当越界时我们让雨点重新回到屏幕最上方或最左方,然后重新开始动画。

resetRandom() - 随机重置,当越界后调用此方法可达到重利用。这个方法里面重置雨点起始位置分两种,一个从最上面出来(x轴),一个从最左边出来(y轴)。

5.在RainView里定义多个雨点对象

现在我们要制造下雨场景只需要制造多个雨点对象,然后像最开始控制一个雨点那样去修改代码。

public class RainView extends BaseView {private ArrayList<RainLine> rainLines;private static final int RAIN_COUNT = 1000; //雨点个数@Overrideprotected void init() {super.init();rainLines = new ArrayList<RainLine>();for (int i = 0; i < RAIN_COUNT; i++) {rainLines.add(new RainLine(windowWidth, windowHeight));}...}@Overrideprotected void drawSub(Canvas canvas) {for(RainLine rainLine : rainLines) {canvas.drawLine(rainLine.getStartX(), rainLine.getStartY(), rainLine.getStopX(), rainLine.getStopY(), paint);}}/*** 动画逻辑处理*/@Overrideprotected void animLogic() {for(RainLine rainLine : rainLines) {rainLine.rain();}}@Overrideprotected boolean needStopAnimThread() {for(RainLine rainLine : rainLines) {if (rainLine.getStartY() >= getWidth()) {rainLine.resetRandom();}}return false;}
}

可以看到只是在原来的单个基础上扩展成了组,现在的效果就基本形成了。

6.随机定制化

如果你觉得所有雨点都是一个方向地不真实,你可以在RainLine中改变deltaXdeltaY为随机值。

    public void initRandom() {...deltaX = random.nextInt(20);deltaY = random.nextInt(30);...}public void resetRandom() {if (random.nextBoolean()) { //随机 true, 雨点从x轴出来...deltaX = random.nextInt(20);} else { //随机 false,雨点从y轴出来...deltaY = random.nextInt(30);}...}

效果就变成为了:

(我好像已经学会了飘雪效果了?)

这场雨看起来像毛毛雨是因为我们y值给的太少,因为移动速度也就是rain()方法里面,y轴方向上的增量就是deltaY,并且stopY = startY + deltaY,所以当deltaY比较小时,雨点既比较短小,又下降比较慢,所以看起来像毛毛雨(飘雪)了。

更改的办法有几个,可以在算下降速度时乘以个比例值,也可以像我这样比较简单的做法,将deltaY = random.nextInt(30);改为deltaY = 20 + random.nextInt(30);

(是不是更逼真了,像大暴雨!因为我偷偷把雨点数增加了哈)

还可以改为随机颜色,剩下的就自己去试了。

(好像又已经学会了礼花的效果了?)

还可以随心所欲地乱改。。。

(我家电视屏幕又花屏了!)


源码地址:https://github.com/Xieyupeng520/AZBarrage/tree/rainview(^3^依旧求星星)


如果你有任何问题,欢迎留言告诉我!~

【Android效果集】下雨效果相关推荐

  1. 6 cocos2dx粒子效果,类图关系,系统原生粒子和自定义粒子效果,粒子编译器软件,爆炸粒子效果,烟花效果,火焰效果,流星效果,漩涡粒子效果,雪花效果,烟雾效果,太阳效果,下雨效果

     1 粒子 示例 2 类图关系 3 系统原生粒子 CCParticleSystem 所有粒子系统的父类 CCParticleSystemPoint. CCParticleSystemQuad 点粒 ...

  2. 【Android效果集】弹幕效果

    之前在网上有看到过iOS的弹幕效果实现,搜了一下发现Android实现弹幕效果的帖子比较少,而且写得都不是很好理解,于是尝试自己做了一下,写成这篇博客,分享出来. 最终效果展示: 实现思路: 1.自定 ...

  3. android下雨动画效果,Android 自定义View(二) 下雨效果

    Rain.gif Android 自定义View(二) 下雨效果 一 实现思路, 雨点用线段表示,通过控制线段的大小和宽度来表示不同的线段. 一个雨点下雨的过程可以表示为一条直线,一次雨点在下雨的过程 ...

  4. 安卓SurfaceView 实现下雨效果

    安卓SurfaceView 实现下雨效果 先来一张效果图 我的思路 下雨每个雨滴用一条线来实现,生成一条线,X轴坐标随机,Y轴不断增加,就形成了下雨的效果 1.首先定义mSurfaceView类,继承 ...

  5. android 仿qq录音动画,Android实现QQ点赞效果动画 Android动画

    版权声明:本文为代码部落原创文章,转载请注明出处. 前言 点赞是现在社交app中比较常用的功能,一个小小的点赞按钮如果能加上一些有趣动画,一来告诉用户你已经点了赞(这是对一些手残党极大的福音),二来人 ...

  6. Android横向伸缩,Android 实现伸缩布局效果示例代码

    最近项目实现下面的图示的效果,本来想用listview+gridview实现,但是貌似挺麻烦的于是就用flowlayout 来addview实现添加伸缩的效果,实现也比较简单. mainActivit ...

  7. Android实现左右滑动效果

    本示例演示在Android中实现图片左右滑动效果.   关于滑动效果,在Android中用得比较多,本示例实现的滑动效果是使用ViewFlipper来实现的,当然也可以使用其它的View来实现.接下来 ...

  8. 区域人工智能集群效果显著 各大省市怎样布局?

    人工智能是引领新一轮科技革命和产业变革的重要驱动力,具有溢出带动性很强的"头雁"效应.近年来,全国各地的工业园区将人工智能作为产业发展的主攻方向,通过集聚行业资源,积极打造国内外知 ...

  9. Android 卡片翻转动画效果

    转载请标明出处:http://blog.csdn.net/android_mnbvcxz/article/details/78570594 Android 卡片翻转动画效果 前言 前端时间开发一款应用 ...

最新文章

  1. ViT(vision transformer)原理快速入门
  2. 大厂程序员和北京户口教师女友买房分歧,要求分配产权怕离婚扯皮
  3. linux安全加固(2)
  4. java实验指导书(实验四)答案_java程序设计实验指导书答案
  5. vb获取数组长度_如何实现数组的二分查找
  6. LeetCode 1172. 餐盘栈(栈 + set)
  7. [TJOI2009] 战争游戏
  8. ORA-12518: TNS: 监听程序无法分发客户机连接
  9. power bi 参数_参数化Power BI报表入门
  10. drools规则引擎中易混淆语法分析_相互触发导致死循环分析
  11. Oracle学习之DATAGUARD(八) Switchover与failover
  12. python实践项目(一)
  13. python 操作access数据库
  14. decimal保留千分位
  15. latex pdf 统计字数
  16. 【正点原子I.MX6U-MINI应用篇】5、嵌入式Linux在LCD上显示BMP、JPG、PNG图片
  17. 转录组+代谢组助力大环内酯类抗生素对藻类抑制作用机制的研究
  18. CIA长期对华开展网络攻击,谍影重重缘起此处
  19. java ee字体_[分享] 23种漂亮的字体代码,
  20. kali中的firefox无法打开:your tab just crashed

热门文章

  1. LeetCode(179) Largest Number
  2. Linux下的dd和cat
  3. postman批量刷接口
  4. 酷睿i5 10300h参数 i5 10300h处理器属于什么水平 i510300h相当于台式机
  5. 程序员的一百万种变现方式之2,努力多赚零花钱
  6. Node.js文件系统-文件操作(一)
  7. 如何准确利用六一,端午等节日节点做好营销
  8. oracle经典习题(一)
  9. 分布式缓存(Redis)连杀
  10. 设计模式:里氏替换原则(详解)