首先是最終實現的效果圖:

先分析一下雨滴的實現:
canvas.drawLine() 繪制分析完了,那么可以直接寫一個類直接繼承 View ,然后重寫 onDraw() 嗎?可以看到效果圖中的雨滴的下落速度很快,那么意味著每一幀都要調用 onDraw() 一次使其重新繪制一次,假如你的 onDraw() 方法里面的渲染代碼稍微有點費時,而 View 的 onDraw() 方法調用是在 UI 線程中,那么繪制出來的效果就不會那么流暢,甚至還會阻塞 UI 線程,所以為了更流暢的效果并且不阻塞 UI 線程,我們這里使用 SurfaceView 來實現。
初識 SurfaceView
SurfaceView 直接繼承自 View,View 必須在 UI 線程中繪制,而 SurfaceView 不同于 View,它可以在非 UI 線程中繪制并顯示在界面上,這意味著你可以自己新開一個線程,然后把繪制渲染的代碼放在該線程中。
Surface 是 Z 軸排序的,SurfaceView 的 Z 軸位置小于它的宿主 Window,代表它總是在自己所在 Window 的后面,既然在后面,那么是怎么顯示的呢?SurfaceView 在其 Window 中打出一個“孔”(其實就是在其宿主 Window 上設置了一塊透明區域來使其能夠顯示),意味著他的兄弟節點的 View 會覆蓋它,例如你可以在 SurfaceView 上方放置按鈕,文本等控件。
要想訪問下面的 Surface ,可以通過 Android 提供給我們的 SurfaceHolder 接口。可以調用 SurfaceView 的 getHolder() 來獲取。
SurfaceView 是有生命周期的,我們必須在它生命周期期間進行執行繪制代碼,所以我們需要監聽 SurfaceView 的狀態(例如創建以及銷毀),這里 Android 為我們提供了 SurfaceHolder.Callback 這個接口來可以讓我們方便的監聽 SurfaceView 的狀態。
那么下面看下 SurfaceHolder.Callback 接口
public interface Callback {// SurfaceView 創建時調用(SurfaceView的窗口可見時) public void surfaceCreated(SurfaceHolder holder);// SurfaceView 改變時調用 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height);// SurfaceView 銷毀時調用(SurfaceView的窗口不可見時) public void surfaceDestroyed(SurfaceHolder holder); }我們的繪制代碼需要在 surfaceCreated 和 surfaceDestroyed 之間執行,否則無效,SurfaceHolder.Callback的回調方法是執行在 UI 線程中的,繪制線程需要我們自己手動創建。
具體可看官方文檔:https://developer.android.google.cn/reference/android/view/SurfaceView.html
View 和 SurfaceView 的使用場景
使用 SurfaceView(實現)
這里我們和自定義 View 類似,寫一個類 DynamicWeatherView 繼承自 SurfaceView,然后為了監聽 SurfaceView 的狀態,所以我們還需要實現 SurfaceHolder.Callback 接口來監聽 SurfaceView 的狀態,接口的回調具體時機上面也已經介紹過了。
public class DynamicWeatherView extends SurfaceView implements SurfaceHolder.Callback{ public DynamicWeatherView(Context context) { this(context, null); } public DynamicWeatherView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DynamicWeatherView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } // SurfaceView 創建時調用(可見) @Override public void surfaceCreated(SurfaceHolder holder) { } // SurfaceView 銷毀時調用(不可見) @Override public void surfaceDestroyed(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { }}上面也提到了,控制 Surface 我們需要 SurfaceHolder 對象,調用 SurfaceView 的 getHolder() 即可獲得,然后為這個 SurfaceHolder 添加一個 SurfaceHolder.Callback 回調,這里就是 DynamicWeatherView 當前對象
private SurfaceHolder mHolder;
mHolder = getHolder();mHolder.addCallback(this);mHolder.setFormat(PixelFormat.TRANSPARENT);
然后實現我們的繪制線程:
private class DrawThread extends Thread { // 用來停止線程的標記 private boolean isRunning = false; public void setRunning(boolean running) { isRunning = running; } @Override public void run() { Canvas canvas; // 無限循環繪制 while (isRunning) { if (mType != null && mViewWidth != 0 && mViewHeight != 0) { canvas = mHolder.lockCanvas(); if (canvas != null) { mType.onDraw(canvas); if (isRunning) { mHolder.unlockCanvasAndPost(canvas); } else { // 停止線程 break; } SystemClock.sleep(1); } } } }}從上面的代碼可以看出 SurfaceView 的更新流程具體為:
// 鎖定畫布并獲得 canvascanvas = mHolder.lockCanvas();// 在 canvas 上進行繪制mType.onDraw(canvas);// 解除鎖定并提交更改mHolder.unlockCanvasAndPost(canvas);
繪制線程代碼量不多,因為具體的繪制代碼在 mType.onDraw(canvas)中,mType 是我們自己定義的一個接口,代表一種天氣類型:
public interface WeatherType { void onDraw(Canvas canvas); void onSizeChanged(Context context, int w, int h);}這樣要想實現不同的天氣類型,只要實現這個接口重寫 onDraw 和 onSizeChanged 方法即可,這里我們實現的是下雨的效果,所以實現了一個 RainTypeImpl 類:
public class RainTypeImpl extends BaseType { // 背景 private Drawable mBackground; // 雨滴集合 private ArrayList<RainHolder> mRains; // 畫筆 private Paint mPaint; public RainTypeImpl(Context context, DynamicWeatherView dynamicWeatherView) { super(context, dynamicWeatherView); init(); } private void init() { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(Color.WHITE); // 這里雨滴的寬度統一為3 mPaint.setStrokeWidth(3); mRains = new ArrayList<>(); } @Override public void generate() { mBackground = getContext().getResources().getDrawable(R.drawable.rain_sky_night); mBackground.setBounds(0, 0, getWidth(), getHeight()); for (int i = 0; i < 60; i++) { RainHolder rain = new RainHolder( getRandom(1, getWidth()), getRandom(1, getHeight()), getRandom(dp2px(9), dp2px(15)), getRandom(dp2px(5), dp2px(9)), getRandom(20, 100) ); mRains.add(rain); } } private RainHolder r; @Override public void onDraw(Canvas canvas) { clearCanvas(canvas); // 畫背景 mBackground.draw(canvas); // 畫出集合中的雨點 for (int i = 0; i < mRains.size(); i++) { r = mRains.get(i); mPaint.setAlpha(r.a); canvas.drawLine(r.x, r.y, r.x, r.y + r.l, mPaint); } // 將集合中的點按自己的速度偏移 for (int i = 0; i < mRains.size(); i++) { r = mRains.get(i); r.y += r.s; if (r.y > getHeight()) { r.y = -r.l; } } } private class RainHolder { /** * 雨點 x 軸坐標 */ int x; /** * 雨點 y 軸坐標 */ int y; /** * 雨點長度 */ int l; /** * 雨點移動速度 */ int s; /** * 雨點透明度 */ int a; public RainHolder(int x, int y, int l, int s, int a) { this.x = x; this.y = y; this.l = l; this.s = s; this.a = a; } }}代碼不難,基本都有注釋,RainHolder 對象代表一個雨滴,每繪制一次然后改變雨滴的位置,然后準備下一次繪制,來實現雨滴的移動。
BaseType 類是我們的一個抽象基類,實現了 DynamicWeatherView.WeatherType 接口,內部有一些公共方法,具體可以看 Demo 中的代碼。
最后我們的 Activity 代碼:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DynamicWeatherView mDynamicWeatherView = (DynamicWeatherView) findViewById(R.id.dynamic_weather_view); mDynamicWeatherView.setType(new RainTypeImpl(this, mDynamicWeatherView)); }}今后要想實現不同的天氣類型,只需要繼承 BaseType 類重寫相關方法即可。
源碼下載:點擊這里
總結
以上就是這篇文章的全部內容了,希望本文的內容對各位Android開發者們能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對武林網的支持。
新聞熱點
疑難解答