【Android效果集】下雨效果
本文参考学习 视频教程-《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()
以及获取了屏幕宽高,所以在子类中可以直接使用windowWidth
和widthHeight
。
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;}
}
除了重构,我还加了几个方法,
首先,构造方法传入了maxX
和maxY
,也就是屏幕宽高,为后面越界处理做准备。
initRandom()
- 初始化的时候雨点随机在屏幕的各个地方。
rain()
- 下雨方法也就是把在RainView
中animLogic()
里面的操作提取出来。
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
中改变deltaX
和deltaY
为随机值。
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效果集】下雨效果相关推荐
- 6 cocos2dx粒子效果,类图关系,系统原生粒子和自定义粒子效果,粒子编译器软件,爆炸粒子效果,烟花效果,火焰效果,流星效果,漩涡粒子效果,雪花效果,烟雾效果,太阳效果,下雨效果
1 粒子 示例 2 类图关系 3 系统原生粒子 CCParticleSystem 所有粒子系统的父类 CCParticleSystemPoint. CCParticleSystemQuad 点粒 ...
- 【Android效果集】弹幕效果
之前在网上有看到过iOS的弹幕效果实现,搜了一下发现Android实现弹幕效果的帖子比较少,而且写得都不是很好理解,于是尝试自己做了一下,写成这篇博客,分享出来. 最终效果展示: 实现思路: 1.自定 ...
- android下雨动画效果,Android 自定义View(二) 下雨效果
Rain.gif Android 自定义View(二) 下雨效果 一 实现思路, 雨点用线段表示,通过控制线段的大小和宽度来表示不同的线段. 一个雨点下雨的过程可以表示为一条直线,一次雨点在下雨的过程 ...
- 安卓SurfaceView 实现下雨效果
安卓SurfaceView 实现下雨效果 先来一张效果图 我的思路 下雨每个雨滴用一条线来实现,生成一条线,X轴坐标随机,Y轴不断增加,就形成了下雨的效果 1.首先定义mSurfaceView类,继承 ...
- android 仿qq录音动画,Android实现QQ点赞效果动画 Android动画
版权声明:本文为代码部落原创文章,转载请注明出处. 前言 点赞是现在社交app中比较常用的功能,一个小小的点赞按钮如果能加上一些有趣动画,一来告诉用户你已经点了赞(这是对一些手残党极大的福音),二来人 ...
- Android横向伸缩,Android 实现伸缩布局效果示例代码
最近项目实现下面的图示的效果,本来想用listview+gridview实现,但是貌似挺麻烦的于是就用flowlayout 来addview实现添加伸缩的效果,实现也比较简单. mainActivit ...
- Android实现左右滑动效果
本示例演示在Android中实现图片左右滑动效果. 关于滑动效果,在Android中用得比较多,本示例实现的滑动效果是使用ViewFlipper来实现的,当然也可以使用其它的View来实现.接下来 ...
- 区域人工智能集群效果显著 各大省市怎样布局?
人工智能是引领新一轮科技革命和产业变革的重要驱动力,具有溢出带动性很强的"头雁"效应.近年来,全国各地的工业园区将人工智能作为产业发展的主攻方向,通过集聚行业资源,积极打造国内外知 ...
- Android 卡片翻转动画效果
转载请标明出处:http://blog.csdn.net/android_mnbvcxz/article/details/78570594 Android 卡片翻转动画效果 前言 前端时间开发一款应用 ...
最新文章
- ViT(vision transformer)原理快速入门
- 大厂程序员和北京户口教师女友买房分歧,要求分配产权怕离婚扯皮
- linux安全加固(2)
- java实验指导书(实验四)答案_java程序设计实验指导书答案
- vb获取数组长度_如何实现数组的二分查找
- LeetCode 1172. 餐盘栈(栈 + set)
- [TJOI2009] 战争游戏
- ORA-12518: TNS: 监听程序无法分发客户机连接
- power bi 参数_参数化Power BI报表入门
- drools规则引擎中易混淆语法分析_相互触发导致死循环分析
- Oracle学习之DATAGUARD(八) Switchover与failover
- python实践项目(一)
- python 操作access数据库
- decimal保留千分位
- latex pdf 统计字数
- 【正点原子I.MX6U-MINI应用篇】5、嵌入式Linux在LCD上显示BMP、JPG、PNG图片
- 转录组+代谢组助力大环内酯类抗生素对藻类抑制作用机制的研究
- CIA长期对华开展网络攻击,谍影重重缘起此处
- java ee字体_[分享] 23种漂亮的字体代码,
- kali中的firefox无法打开:your tab just crashed