github地址:https://github.com/shuaijia/JSBaseDemo/blob/master/app/src/main/java/com/jia/demo/view/SnowView.java
/**
* Description: 雪花效果实体类
* Created by jia on 2017/12/25.
* 人之所以能,是相信能
*/
public class Snow {
private float x;
private float y;
private int alfa;
private float size;
private float speed;
private int srcType;
public Snow(float x, float y, int alfa, float size, float speed, int srcType) {
this.x = x;
this.y = y;
this.alfa = alfa;
this.size = size;
this.speed = speed;
this.srcType = srcType;
}
...// get、set方法省略
}
实现思路:
说起这种雪花纷飞的效果,大家都会立刻想到用属性动画,通过各种动画组合、插值器的使用(当然使用贝塞尔曲线会更炫),就可以很轻松的实现如上效果,但我们今天换种思路来实现:
因为所有的雪花需要实时在移动位置,所以想开启子线程去控制所以雪花位置,但因为在子线程中刷新view,就采用SurfaceView来实现。
SurfaceView继承之View,但拥有独立的绘制表面,即它不与其宿主窗口共享同一个绘图表面,可以单独在一个线程进行绘制,并不会占用主线程的资源。
public SnowView(Context context, AttributeSet attrs) {
super(context, attrs);
surfaceHolder = this.getHolder();
surfaceHolder.addCallback(this);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bgBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.snow_bg);
init();
}
private void init() {
DisplayMetrics dm = getResources().getDisplayMetrics();
snows = new ArrayList<>();
float x, y, size, speed;
int alfa, srcType;
for (int i = 0; i < maxCount; i++) {
x = (float) Math.floor(Math.random() * dm.widthPixels);//初始X坐标
y = (float) Math.floor(Math.random() * dm.heightPixels);//初始Y坐标
size = (float) ((Math.random() * 15f) + 20f);//初始半径
speed = (float) ((Math.random() * 6) + 5);
alfa = (int) Math.floor(100 * Math.random() + 155);
srcType = (int) (Math.random() + 0.5);
snows.add(new Snow(x, y, alfa, size, speed, srcType));
}
}
初始化SnowView,我们定义一屏雪花数(如100),循环100次,使用随机数设置雪花位置、大小、透明度等属性,并放入集合中。
/**
* 绘制进程
*/
class DrawThread extends Thread {
public boolean isRunning = false;
private Canvas canvas;
public DrawThread() {
isRunning = true;
}
@Override
public void run() {
super.run();
while (isRunning) {
synchronized (surfaceHolder) {
// Start editing the pixels in the surface. The returned Canvas can be used to draw into the surface's bitmap.
canvas = surfaceHolder.lockCanvas();
drawSprite(canvas);
for (int i = 0; i < maxCount; i++) {
curSnow = snows.get(i);
float size = curSnow.getSize();
float speed = curSnow.getSpeed();
int alfa = curSnow.getAlfa();
float x = curSnow.getX();
float y = curSnow.getY() + speed;
int type = curSnow.getSrcType();
if (y >= canvas.getHeight() || x >= canvas.getWidth()) {
y = 0;
x = (float) Math.floor(Math.random() * canvas.getWidth());//初始X坐标
}
mPaint.setAlpha(alfa);
Bitmap snowBitmap;
if (type == 1) {
snowBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.snow1);
} else {
snowBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.snow2);
}
RectF rect = new RectF(x, y, x + size, y + size);
canvas.drawBitmap(snowBitmap, null, rect, mPaint);
snows.set(i, new Snow(x, y, alfa, size, speed, type));
}
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
public void stopThread() {
isRunning = false;
boolean workIsNotFinish = true;
while (workIsNotFinish) {
try {
this.join();// 保证run方法执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
workIsNotFinish = false;
}
}
}
private void drawSprite(Canvas canvas) {
//清屏操作
canvas.drawBitmap(bgBitmap, null, new Rect(0, 0, canvas.getWidth(), canvas.getHeight()), null);
}
在run方法中获取到当前绘制的canvas,然后循环进行绘制,绘制完成后surfaceHolder.unlockCanvasAndPost(canvas),将画布显示在屏幕上。
注意整个循环执行次数多,但我们必须保证全部绘制完再切换线程,所以我们使用synchronized关键字。
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (null == mDrawThread) {
mDrawThread = new DrawThread();
mDrawThread.start();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (null != mDrawThread) {
mDrawThread.stopThread();
}
}
在surface创建的回调中开启线程,在destroy方法中关闭线程,就ok了!