【Android效果集】下雨效果
2015-10-23 00:12
531 查看
本文参考学习视频教程-《Android 粒子效果之雨》
效果图:
本文在《【Android效果集】弹幕效果 》基础上实现,建议先阅读完再看本文。
跟着上一篇介绍弹幕效果的文章相比,这一篇其实和上一篇很类似,虽然效果看起来大相径庭,看下实现就会发现很相似,可以学会来然后举一反三做出很多好玩的动画效果!~
我们首先来分析一下每个雨点效果,每个雨点其实就是一条倾斜直线,从屏幕上/左方出来,到屏幕下/右方消失,期间沿着直线的方向移动。
不是很像弹幕吗?弹幕是从右边出来,水平移动到左边出去。
2.RainView能够自动从最上面移动到最下边,且有一定倾斜角度,移动过程中一直保持着同一个倾斜角度
3.重构提取出单个雨滴类,将RainView改为包含多个雨滴代表一场雨
4.随机定制化,比如倾斜角度,颜色等
可以看到我在BaseView中提取出了
为什么要这么做呢?我刚刚也讨论过了,下雨效果和弹幕效果实现十分相似,可以说它们的实现代码有很多重合的地方,而这些重复的地方正是上面BaseView里面的代码。我们写代码遇到有大量重复代码的时候怎么办?提取抽象类!正是基于此点考虑才重构的。
而如果我们想让一条倾斜的直线倾斜着移动,怎么做?难道还要算角度和比例吗?
其实很简单,只需要让“这条线”上的两个坐标点的
这个时候运行,以及能看到一条雨点滴落啦!~(对了前提别忘了把自定义View加到主布局里去)
这是因为在下雨的场景中,雨点的数量是非常庞大的,从几百到几千都是可能的,而我们在
我们提取出专门的一个雨点类,然后在
除了重构,我还加了几个方法,
首先,构造方法传入了
可以看到只是在原来的单个基础上扩展成了组,现在的效果就基本形成了。
效果就变成为了:
(我好像已经学会了飘雪效果了?)
这场雨看起来像毛毛雨是因为我们
更改的办法有几个,可以在算下降速度时乘以个比例值,也可以像我这样比较简单的做法,将
(是不是更逼真了,像大暴雨!因为我偷偷把雨点数增加了哈)
还可以改为随机颜色,剩下的就自己去试了。
(好像又已经学会了礼花的效果了?)
还可以随心所欲地乱改。。。
(我家电视屏幕又花屏了!)
源码地址:https://github.com/Xieyupeng520/AZBarrage/tree/rainview(^3^依旧求星星)
如果你有任何问题,欢迎留言告诉我!~
效果图:
本文在《【Android效果集】弹幕效果 》基础上实现,建议先阅读完再看本文。
跟着上一篇介绍弹幕效果的文章相比,这一篇其实和上一篇很类似,虽然效果看起来大相径庭,看下实现就会发现很相似,可以学会来然后举一反三做出很多好玩的动画效果!~
我们首先来分析一下每个雨点效果,每个雨点其实就是一条倾斜直线,从屏幕上/左方出来,到屏幕下/右方消失,期间沿着直线的方向移动。
不是很像弹幕吗?弹幕是从右边出来,水平移动到左边出去。
实现思路
1.在上一篇弹幕项目基础上,重构出一个BaseView,自定义RainView代表一个雨滴,继承自BaseView2.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); @Override protected 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 { @Override public 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); } /** * 初始化 */ @Override protected void init() { super.init(); } /** * 画子类 * @param canvas */ @Override protected void drawSub(Canvas canvas) { } /** * 动画逻辑处理 */ @Override protected void animLogic() { } /** * 里面根据当前状态判断是否需要返回停止动画 * * @return 是否需要停止动画thread */ @Override protected boolean needStopAnimThread() { return false; } /** * 动画结束后做的操作,比如回收资源 */ @Override protected 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; @Override protected void init() { startX = 0; startY = 30; stopX = startX + deltaX; stopY = startY + deltaY; paint = new Paint(); if (paint !=null) { paint.setColor(0xffffffff); //白色 } } @Override protected void drawSub(Canvas canvas) { canvas.drawLine(startX, startY, stopX, stopY, paint); } @Override protected 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; //雨点个数 @Override protected void init() { super.init(); rainLines = new ArrayList<RainLine>(); for (int i = 0; i < RAIN_COUNT; i++) { rainLines.add(new RainLine(windowWidth, windowHeight)); } ... } @Override protected void drawSub(Canvas canvas) { for(RainLine rainLine : rainLines) { canvas.drawLine(rainLine.getStartX(), rainLine.getStartY(), rainLine.getStopX(), rainLine.getStopY(), paint); } } /** * 动画逻辑处理 */ @Override protected void animLogic() { for(RainLine rainLine : rainLines) { rainLine.rain(); } } @Override protected 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^依旧求星星)
如果你有任何问题,欢迎留言告诉我!~
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories