爲了增強對自定義 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);
}
}
複製代碼