自定義View合輯(3)-下雨
為了加強對自定義 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); } }