android自定义view粒子效果之雨(not surfaceview)

来源:互联网 发布:linux locate which 编辑:程序博客网 时间:2024/05/01 09:17

首先声明的是,粒子效果不一定是用surfaceview来实现的,只要可以绘制和更新绘制既可以做到很多精彩的画面。


简单的说一下自定义view吧,其实就是继承View,然后生成几个构造方法,这样就是一个简单的自定义view。

public class MyView extends View{public MyView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}public MyView(Context context, AttributeSet attrs) {super(context, attrs);}public MyView(Context context) {super(context);}}
但是它什么也做不了,什么也不显示,很多人都知道,覆盖onDraw(Canvas)方法,没错

@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//做自己想做的事情(绘制)}

这样,一个可以显示自己绘制的view已经完成一半了,为什么说一半,因为还不会动,但是这会有很多逻辑,而这些逻辑不应该出现在主线程中,所以用Handler来做这部分事情,开启一个处理逻辑的线程通过Handler来更新绘制。

private Runnable run = new Runnable() {public void run() {long curTime = 0;while (true) {curTime = System.currentTimeMillis();//此处加入逻辑mHandler.sendEmptyMessage(0);curTime = System.currentTimeMillis() - curTime;try {if(curTime < 30){Thread.sleep(30 - curTime);}} catch (InterruptedException e) {break;}}}};private void logic(){//此处加入逻辑处理}private Handler mHandler = new Handler() {public void handleMessage(android.os.Message msg) {invalidate();};};

好吧,这样基本上一个自定义view的框架就出来了。

铺垫好了后面就会好理解了,绘制和动画基本上都是在onDraw里面和logic里面完成,其他的粒子效果也是。

粒子效果在我看来,其实就是具有相同属性的对象集合运动,说白了,就是一个列表里面有多个对象,每个对象自己都在动,放在一起,就出现了粒子效果。再简单点说,只要定义一个类,这个类有绘制和运动就可以了,然后实例化多个,放在列表里面,绘制的时候遍历每个元素就可以了。


所以要在自定义view中加入一个ArrayList就可以了,然后在onDraw和logic里面遍历list里面的每个元素。说到这里了,其实核心也出来了,粒子效果最终追述到的根源,还是单个元素的定制。


那么粒子效果有大家想象的那么难吗,其实没有,起码我写的几个都是在100行代码左右(单个元素),那么,来看看下雨的效果怎么实现的吧。


如果在手机上看,会很清楚和流畅,因为我用视频录制转换成gif,这样分辨率就小了很多,而且帧率也变了。


单个雨点实际上就是一条线,线的大小可以根据自己的喜好定制随机范围。那么需要定义一些基本的变量。显示的宽,高,随机数。

/** * 显示区域的宽度 */protected int width;/** * 显示区域的高度 */protected int height;/** * 效果元素的随机对象 */protected Random rand;

上面的宽高是通过View的getWidth()和getHeight()来传递的。rand也是在构造的时候就已经实例化了。

public EffectItem(int width, int height){this.width = width;this.height = height;rand =new Random();}

上面的EffectItem其实就是Rain,只不过我把它抽离出来了,方便以后其他粒子复用,但是不影响阅读,把它看成Rain就可以了。绘制的区域出来了,那么绘制的线呢?这就通过rand来生成,先看看系统绘制直线的方法:

public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) {        native_drawLine(mNativeCanvas, startX, startY, stopX, stopY, paint.mNativePaint);    }

需要起始x,y和终点x,y。系统有个Rect的类,它有left, top, right, bottom。所以可以使用这个类来辅助。

<span style="white-space:pre"></span>int x = rand.nextInt(width);int y = rand.nextInt(height);int w = rand.nextInt(size / 2);int h = rand.nextInt(size);w = w > h ? h : w;point.left = x;point.top = y;point.right = x - w;point.bottom = y + h;

size的值可以自己定,我定的是50像素,这样一条线段的坐标就出来了,只要draw里面使用就可以了

public void draw(Canvas canvas){canvas.drawLine(point.left, point.top, point.right, point.bottom, paint);}

还记得我前面说的吗,每个元素都会在自定义的View里面的onDraw(Canvas)里面遍历绘制,所以不用途担心这里面的draw不起作用。ok,绘制线出来了,但是,你会发现这个线不动,因为没有逻辑让它动起来。


那么该怎么动呢,对,用速度,要让它往下落,那么y的坐标就一直在变,所有y方向有有个速度,但是你又会发现,一个斜杠在往下掉,不符合自然规律,应该让它按照它的角度和方向落下,听的好难啊啊,其实就2行代码,上面代码你会很奇怪有个w,h,这次有用了,稍微在纸上画画,你会发现这条线段是按照一定规律,即一个系数(是几不重要),只要x方向乘以w和y方向乘以h就可以了,所以如果定义速度的话就好办多了。

private void reset(){int x = rand.nextInt(width);int y = rand.nextInt(height);int w = rand.nextInt(size / 2);int h = rand.nextInt(size);w = w > h ? h : w;point.left = x;point.top = y;point.right = x - w;point.bottom = y + h;int speedX = w;int speedY = h;speedX = speedX == 0  ? 1 : speedX;speedY = speedY == 0  ? 1 : speedY;speedX = speedX > speedY ? speedY : speedX;speed.x = -speedX;speed.y = speedY;}
上面就是生成一个雨点的方法,speed是个Point的对象,这样可以表示x和y方向上的速度。

生成和绘制都有了,就差运动了

public void move(){point.left += speed.x;point.top += speed.y;point.right = point.right + speed.x;point.bottom = point.bottom + speed.y;if(point.left < 0 || point.left > width || point.bottom > height){reset();}speed.y += rand.nextBoolean() ? 1 : 0;}

上面就是运动的方法,就是简单的把位置变换了下,但是当出绘制区域的时候,会重新生成一个雨点,所以,屏幕上实际上就是固定数目的雨点的不停的变换位置。最后的y方向的速度是不定时的将y方向加入加速度,这样就会有重力似的效果。每个元素move方法其实就是在自定义View中的logic中被遍历的。

到此位置,一个雨点的生成,绘制,运动都完成了,只要加入到list中然后在View里面遍历就基本完成了。一个雨点完整的代码如下:

public class RainPoint extends EffectItem{private Paint paint = new Paint();privatefinal int size = 50;//长度在0-50像素private Rect point;//雨点private Point speed;//雨点x,y方向速度public RainPoint(int width, int height){super(width, height);point = new Rect();speed = new Point();paint.setColor(0xffffffff);reset();}public void draw(Canvas canvas){canvas.drawLine(point.left, point.top, point.right, point.bottom, paint);}public void move(){point.left += speed.x;point.top += speed.y;point.right = point.right + speed.x;point.bottom = point.bottom + speed.y;if(point.left < 0 || point.left > width || point.bottom > height){reset();}speed.y += rand.nextBoolean() ? 1 : 0;}private void reset(){int x = rand.nextInt(width);int y = rand.nextInt(height);int w = rand.nextInt(size / 2);int h = rand.nextInt(size);w = w > h ? h : w;point.left = x;point.top = y;point.right = x - w;point.bottom = y + h;int speedX = w;int speedY = h;speedX = speedX == 0  ? 1 : speedX;speedY = speedY == 0  ? 1 : speedY;speedX = speedX > speedY ? speedY : speedX;speed.x = -speedX;speed.y = speedY;}}
而EffectItem代码如下:
<pre name="code" class="java">public abstract class EffectItem implements EffectBase{/** * 显示区域的宽度 */protected int width;/** * 显示区域的高度 */protected int height;/** * 效果元素的随机对象 */protected Random rand;public EffectItem(int width, int height){this.width = width;this.height = height;rand =new Random();}}
EffectBase接口定义了两个方法:
public interface EffectBase {/** * 绘制效果 * @param canvas */public void draw(Canvas canvas);/** * 效果元素变化 */public void move();}

这样,一个粒子的完整过程就出来了,而后面其它的粒子也是按照这个模型就行设计的。剩下的就是在list里面加入多个实例了,然后按部就班的在onDraw里面遍历每个元素的draw方法,在logic里面遍历move方法。

<span style="white-space:pre"></span><span style="color:#ff0000;">最重要的是,因为这是自定义View,可以像使用其他View一样,放到布局里面,所以丰富它,可以有无限的遐想展示空间。</span>


下面我再贴出我丰富的几个丰富的效果,后面会说到怎么实现。

变化雨点颜色(在xml中定义属性就可以了)

每个雨点都随机颜色(在xml中定义属性就可以了)


还可以进行区域clip哦


好了,如果谁有好的想法也可以共享一下哦,看看如果能实现,看看效果。详细代码:https://github.com/xianfeng99/Particle

github上面的代码可能没有及时更新。

0 0