为了增强对自定义 View 的认知以及开发能力,我计划这段时间陆续来完成几个难度从易到难的自定义 View,并简单的写几篇博客来进行介绍,全部的代码也都会开源,也但愿读者能给个 star 哈 GitHub 地址:github.com/leavesC/Cus… 也能够下载 Apk 来体验下:www.pgyer.com/CustomViewjava
先看下效果图:git
效果图中的“雨”其实只是一条条稍微倾斜的线条,经过构造多条 X/Y 坐标 随机生成的 Line 对象,而后不断改变其 Y 坐标,就能够模拟出这种“下雨”的效果github
须要有一个内部类用来抽象“雨丝”这个概念canvas
private static final class Line {
private float startX;
private float startY;
private float stopX;
private float stopY;
}
复制代码
也须要一个容器来承载全部“雨丝”dom
private final List<Line> lineList = new LinkedList<>();
复制代码
提供两个方法用于初始化 Line 以及在 Line 超出屏幕时再次重置其坐标ide
private Line getRandomLine() {
Line line = new Line();
resetLine(line);
return line;
}
private void resetLine(Line line) {
line.startX = nextFloat(0, getWidth() - 3.0f);
line.startY = 0;
//使之有一点点倾斜
line.stopX = line.startX + nextFloat(3.0f, 6.0f);
line.stopY = line.startY + nextFloat(30.0f, 50.0f);
}
//返回 min 到 max 之间的随机数值,包括 min,不包括 max
private float nextFloat(float min, float max) {
return min + random.nextFloat() * (max - min);
}
复制代码
前文说了,是经过不断改变 Line 的 Y 坐标来模拟出这种“下雨”的效果,所以就须要定时且频繁地刷新页面,为了提升绘制效率,此处的自定义 View 就不直接继承于 View ,而是继承于 SurfaceView。此处采用线程池来进行定时刷新spa
private ScheduledExecutorService scheduledExecutorService;
private Runnable runnable = new Runnable() {
@Override
public void run() {
Canvas canvas = surfaceHolder.lockCanvas();
if (canvas != null) {
int tempDegree = degree;
int size = lineList.size();
if (size < tempDegree) {
//这里须要逐渐添加Line,才能使得Line的高度良莠不齐
lineList.add(getRandomLine());
} else if (size > tempDegree) {
Line tempLine = null;
for (Line line : lineList) {
if (line.startY >= getHeight()) {
tempLine = line;
break;
}
}
if (tempLine != null) {
lineList.remove(tempLine);
}
}
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
for (Line line : lineList) {
//重置超出屏幕的 Line 的坐标
if (line.startY >= getHeight()) {
resetLine(line);
continue;
}
canvas.drawLine(line.startX, line.startY, line.stopX, line.stopY, paint);
line.startY = line.startY + speed;
line.stopY = line.stopY + speed;
}
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
};
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
Log.e(TAG, "surfaceCreated");
if (scheduledFuture != null && !scheduledFuture.isCancelled()) {
scheduledFuture.cancel(false);
scheduledFuture = null;
}
scheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(runnable, 300, 10, TimeUnit.MILLISECONDS);
}
复制代码
雨的密集程度以及下落速度这两个属性是经过两个全局变量来进行控制的,分别是 degree 和 speed线程
在每次刷新页面前,Line 的 Y 坐标 都是会向下移动的,其每次移动的距离 speed 就是雨的下落速度了3d
for (Line line : lineList) {
//重置超出屏幕的 Line 的坐标
if (line.startY >= getHeight()) {
resetLine(line);
continue;
}
canvas.drawLine(line.startX, line.startY, line.stopX, line.stopY, paint);
line.startY = line.startY + speed;
line.stopY = line.stopY + speed;
}
复制代码
而雨的密集程度则由 lineList 的 size 大小来体现,所以当 degree 改变时,须要向 lineList 移除或者添加数据code
int tempDegree = degree;
int size = lineList.size();
if (size < tempDegree) {
//这里须要逐渐添加Line,才能使得Line的高度良莠不齐
lineList.add(getRandomLine());
} else if (size > tempDegree) {
Line tempLine = null;
for (Line line : lineList) {
if (line.startY >= getHeight()) {
tempLine = line;
break;
}
}
if (tempLine != null) {
lineList.remove(tempLine);
}
}
复制代码