#|粒子效果之雨的实现
创作启发 在极客学院看到FreeHeart老师讲解的《Android粒子效果之雨》,跟着学习了一下。但是运行效果中,雨点数量明显大于设定的数量且越来越多。为了解决这个问题,所以自己重新写了一遍代码,进行了封装,实现自己喜欢的效果。
【#|粒子效果之雨的实现】接下来,让我们一步一步实现这个效果吧。
第一步:封装一个基础View 首先,新建一个 BaseRainView.class ,并继承系统自带的 View
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
/**
* @author ailsa
* * 2019/3/7 0007
* * BaseRainView,下雨效果的基础View
*/
public class BaseRainView extends View {public BaseRainView(Context context) {
super(context);
}public BaseRainView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}}
接下来,我们需要用到 onDraw() 方法进行雨点的绘制,所以需要在BaseRainView中添加onDraw()方法。在下图中我们可以看到父类View中的onDraw()方法没有实现的内容,所以在
BaseRainView.class
的onDraw()中可以将super.onDraw(canvas);
这行代码删掉文章图片
此时
BaseRainView.class
中的onDraw()方法没有任何实现代码 @Override
protected void onDraw(Canvas canvas) { }
我们知道,雨点是从上到下降落的,所以我们自onDraw()中绘制的雨点也应该是从上到下不断移动的,那么我们可以用什么实现呢??
——我们可以使用postInvalidate()方法不断调用onDraw()进行重绘,重绘是通过改变雨点的位置来实现每次的绘制位置的不同,所以我们还需要使用一个Thread进行位置的改变(UI线程不允许操作数据)。所以我们在
BaseRainView.class
中添加如下代码class MThread extends Thread {
@Override
public void run() {
while (true) {
postInvalidate();
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
我们还差些什么呢??
——没有实现雨点绘制的代码,包括使用画笔画布绘制雨点,雨点位置的改变,多个雨点的效果。为此,我们需要三个方法 initRainDrops() 、drawRainDrops() 、moveRainDrops() 用来实现这些功能。此时,
BaseRainView.class
的所有内容如下/**
* @author ailsa
* * 2019/3/7 0007
* * BaseRainView,下雨效果的基础View
*/
public abstract class BaseRainView extends View {
/**
* 自定义线程,实现雨的移动效果
*/
private MThread thread;
public BaseRainView(Context context) {
super(context);
}public BaseRainView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}/**
* 初始化所有雨点 [ 子类实现 ]
*/
protected abstract void initRainDrops();
/**
* 绘制所有雨点 [ 子类实现 ]
*
* @param canvas 画布
*/
protected abstract void drawRainDrops(Canvas canvas);
/**
* 移动所有雨点 [ 子类实现 ]
*/
protected abstract void moveRainDrops();
@Override
protected void onDraw(Canvas canvas) {
if (thread == null) {
initRainDrops();
// 初始化所有雨点
thread = new MThread();
thread.start();
} else {
drawRainDrops(canvas);
// 绘制所有雨点
}
}class MThread extends Thread {
@Override
public void run() {
while (true) {
moveRainDrops();
// 移动所有雨点
postInvalidate();
// 调用onDraw()重绘
try {
Thread.sleep(30);
// 休眠30ms后再次执行移动逻辑
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
【注意】
initRainDrops()这行代码一定要放在onDraw()方法中,且只调用一次就够了。如果放在moveRainDrops()的while循环中,将导致每一次运行Thread都会向list中添加item。最终,整个界面的雨点会越来越多。这就是我写本文的初衷。至此,BaseRainView的封装就实现好了,让我们开始下一步。
第二步:实现单个雨点的绘制 我们新建一个 RainDrop.class 文件,实现单个雨点下落效果。
我们思考一下,这个文件里需要什么内容呢??
——需要绘制出一个雨点,实现该雨点移动(下落)。所以我们自定义两个方法 drawSingleRainDrop(Canvas canvas) 、moveSingleRainDrop() 。
我们可以用canvas的drawLine()方法绘制一条直线(雨点),该方法需要5个参数startX,startY,stopX,stopY,paint,所以我们定义这5个参数,并进行初始化。
/**
* 画笔在画布x/y方向的起始、终止位置
*/
private int startX;
private int startY;
private int stopX;
private int stopY;
/**
* 画笔
*/
private Paint paint;
/**
* 参数初始化
*/
private void init() {
paint = new Paint();
paint.setColor(0xffffffff);
startX = 100;
startY = 100;
stopX = startX;
stopY = startY + 30;
}
接下来,我们自定义一个drawDrop(Canvas canvas)方法,使用canvas绘制雨点形状
/**
* 绘制单个雨点
*
* @param canvas 画布
*/
void drawSingleRainDrop(Canvas canvas) {
canvas.drawLine(startX, startY, stopX, stopY, paint);
}
为了实现雨点的移动效果,我们还需要自定义一个moveDrop()方法,该方法确定了雨点每次移动多少距离,雨点移出屏幕后应从屏幕上方再次向下移动
/**
* 单个雨点的移动逻辑
*/
void moveSingleRainDrop() {
startY += 30;
stopY += startY;
if (startY > height) {
init();
}
}
因为需要判断屏幕的高度,所以我们还需要外部传入一个height参数
/**
* 屏幕高度
*/
private int height;
RainDrop(int height) {
this.height = height;
init();
}
第三步:实现多个雨点的绘制 我们新建一个 RainView.class ,继承自BaseRainView,实现一场雨的效果。
import android.content.Context;
import android.graphics.Canvas;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
/**
* @author ailsa
* * 2019/3/7 0007
* * RainView,下雨效果的具体实现
*/
public class RainView extends BaseRainView {public RainView(Context context) {
super(context);
}public RainView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}@Override
protected void initRainDrops() {}@Override
protected void drawRainDrops(Canvas canvas) {}@Override
protected void moveRainDrops() {}
}
我们可以使用List来存储多个雨点。接下来,我们声明一个List并在构造函数里初始化
/**
* 雨点集合
*/
private List rainDrops;
public RainView(Context context) {
super(context);
rainDrops= new ArrayList<>();
}public RainView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
rainDrops= new ArrayList<>();
}
我们使用for循环向List中添加雨点
@Override
protected void initRainDrops() {
for (int i = 0;
i < 5;
i++) {
rainDrops.add(new RainDrop(getHeight()));
}
}
我们使用for循环在drawRainDrops()中绘制所有雨点
@Override
protected void drawRainDrops(Canvas canvas) {
for (RainDrop rainDrop : rainDrops) {
rainDrop.drawSingleRainDrop(canvas);
}
}
同样的,我们使用for循环在moveRainDrops()中移动所有的雨点
@Override
protected void moveRainDrops() {
for (RainDrop rainDrop : rainDrops) {
rainDrop.moveSingleRainDrop();
}
}
这样,整个逻辑就搭建完成了。但是我们在模拟器中看到的只是一个雨点,并没有多个雨点的效果呀,这是为什么呢??
因为我们绘制的所有雨点的位置都是一样的,所有雨点重叠在一起,导致我们只看到一个雨点的效果。
那我们可以怎么解决呢??
要想各个雨点独立,最主要的就是改变它们的位置,但是我们不可能给每个雨点单独初始化位置,如果有成百上千的雨点,每个都初始化位置,可想而知,该是多么糟糕呀。我们可以使用随机数Random,如此每个雨点都能有一个不同的位置。所以我们来改一下
RainDrop.class
文件。优化
- 使用随机数初始化雨点位置
/**
* 随机数
*/
private Random random;
/**
* 参数初始化
*/
private void init() {
random = new Random();
startX = random.nextInt(width);
startY = random.nextInt(height);
...
}
我们可以看到,startY 的随机数范围是整个屏幕的高度,startX 的随机数范围是整个屏幕的宽度,所以我们还需要外部传入一个width参数
/**
* 屏幕宽度
*/
private int width;
RainDrop(int height, int width) {
this.height = height;
this.width = width;
init();
}
- 使用speed参数控制雨点下落速度
/**
* 速度
*/
private float speed;
/**
* 参数初始化
*/
private void init() {
speed = 0.2f + random.nextFloat();
}
/**
* 单个雨点的移动逻辑
*/
void move() {
startY += 30 * speed;
stopY += 30 * speed;
if (startY > height) {
init();
}
}
- 提取offsetX、offsetY
/**
* 线条在x/y方向的偏移量
*/
private int offsetX;
private int offsetY;
如果我们想要实现雨点垂直下落的效果,那么只需要从外部传入一个offsetY值,初始化offsetX为0即可
RainItem(int height, int width,int offsetY) {
this.height = height;
this.width = width;
// 参数初始化
offsetX = 0;
this.offsetY = offsetY;
init();
}
如果我们想实现雨点倾斜下落效果(风吹),需要从外部传入offsetX值和offsetY值
RainItem(int height, int width, int offsetX, int offsetY) {
this.height = height;
this.width = width;
this.offsetX = offsetX;
this.offsetY = offsetY;
init();
}
此时,我们还需要修改一下init()、move()方法中的内容
/**
* 参数初始化
*/
private void init() {
startX = random.nextInt(width);
startY = random.nextInt(height);
stopX = startX + offsetX;
stopY = startY + offsetY;
speed = 0.2f + random.nextFloat();
}
/**
* 单个雨点的移动逻辑
*/
void move() {
startX += offsetX * speed;
stopX += offsetX * speed;
startY += offsetY * speed;
stopY += offsetY * speed;
if (startY > height) {
init();
}
}
至此,
RainDrop.class
文件的内容就修改完毕了。我们再来看一下RainView文件,里面有一个数值也可以提取成参数,即List的item数量
/**
* 雨点数量
*/
private int rainDropCount= 20;
@Override
protected void initRainDrops() {
for (int i = 0;
i < rainDropCount;
i++) {
itemList.add(new RainItem(getHeight(), getWidth()));
}
}
如此,所有的修改都完成了,我们得到了一个相对完美的自定义View。
看一下效果图
文章图片
文章图片
该项目的示例地址为:https://github.com/Ailsa2019/starfiled ,欢迎大家学习探讨。
推荐阅读
- 了解自然大气粒子对气候的影响
- Cesium|Cesium 粒子系统学习
- Android自定义view实现圆环进度条效果
- 爬取网易云音乐
- 球幕系统哪些是决定显示效果的关键因素()
- 传播不限,发挥传播的效果是关键。
- 那些决定拉伸效果的细节你都做到位了吗
- Nodejs爬虫实战项目之链家
- 21.css3轮播效果
- 带辉光的文字和图片效果