为了加强对自定义 View 的认知以及开发能力,我计划这段时间陆续来完成几个难度从易到难的自定义 View,并简单的写几篇博客来进行介绍,所有的代码也都会开源,也希望读者能给个 star 哈
GitHub 地址:https://github.com/leavesC/CustomView
也可以下载 Apk 来体验下:https://www.pgyer.com/CustomView
先看下效果图:
一、抽象概念
效果图中的“雨”其实只是一条条稍微倾斜的线条,通过构造多条 X/Y 坐标 随机生成的 Line 对象,然后不断改变其 Y 坐标,就可以模拟出这种“下雨”的效果
需要有一个内部类用来抽象“雨丝”这个概念
private static final class Line {
private float startX;
private float startY;
private float stopX;
private float stopY;
}
也需要一个容器来承载所有“雨丝”
private final List<Line> lineList = new LinkedList<>();
提供两个方法用于初始化 Line 以及在 Line 超出屏幕时再次重置其坐标
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。此处采用线程池来进行定时刷新
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 就是雨的下落速度了
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 移除或者添加数据
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);
}
}