前言
摘自《Android群英傳》
Android提供了View進行繪圖處理锹淌,View可以滿足大部分的繪圖需求,但在某些時候也會心有余而力不足窗慎。我們知道,View通過刷新來重繪視圖起宽,Android 系統(tǒng)通過發(fā)出VSYNC信號來進行屏幕的重繪,刷新的時間間隔為16ms济榨。如果在16ms內View完成了你所需要執(zhí)行的所有操作燎含,那么用戶在視覺上就不會產生卡頓的感覺;而如果執(zhí)行的操作邏輯太多腿短,特別是需要頻繁刷新的界面上屏箍,例如游戲界面,就會不斷阻塞主線程橘忱,從而導致畫面卡頓赴魁。很多情況下,在自定義View的log中會看到如下的警告:
“Skipped 47 frames! The application may be doing too much work on its main thread.”
為了避免這一問題的產生钝诚,Android系統(tǒng)提供了SurfaceView組件颖御。
View 和 SurfaceView 的區(qū)別
- View 主要適用于主動更新的情況下,而 SurfaceView 主要適用于被動更新凝颇,例如頻繁地刷新潘拱。
- View 在主線程中對畫面進行刷新,而 SurfaceView 通常會通過一個子線程來進行頁面的刷新拧略。
- View 在繪圖時沒有使用雙緩沖機制芦岂,而 SurfaceView 在底層實現(xiàn)機制中就已經(jīng)實現(xiàn)了雙緩沖機制。
總結就是垫蛆,如果你的自定義View需要頻繁刷新禽最,或者刷新時數(shù)據(jù)處理量比較大,那么你就可以考慮使用 SurfaceView 來取代 View 了袱饭。
SurfaceView 的使用
SurfaceView 的使用雖然比 View 復雜川无,但是 SurfaceView 在使用時,有一套使用的模板代碼虑乖,大部分的 SurfaceView 繪圖操作都可以套用這樣的模板代碼來進行編寫懦趋。因此 SurfaceView 的使用會更加簡單。
創(chuàng)建一個 SurfaceView 的模板
1疹味、 創(chuàng)建 SurfaceView
創(chuàng)建自定義的 MySurfaceView 繼承自 SurfaceView 仅叫,并實現(xiàn)兩個接口——SurfaceHolder.Callback, Runnable,同時實現(xiàn)其接口方法佛猛,如下:
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
@Override
public void run() {
}
}
2惑芭、初始化 SurfaceView
在自定義 SurfaceView 的構造方法中,需要對 SurfaceView 進行初始化继找。通常需要定義以下三個成員變量,如下:
private SurfaceHolder mHolder;
//用于繪圖的canvas
private Canvas mCanvas;
//子線程標志位
private boolean mIsDrawing;
初始化方法就是初始化一個 SurfaceHolder 對象并注冊 SurfaceHolder 的回調方法逃沿,如下:
mHolder = getHolder();
mHolder.addCallback(this);
另外兩個成員變量——Canvas 和標志位婴渡。使用 Canvas 來進行繪圖幻锁;使用標志位來控制之前提到的用于繪制的子線程。
3边臼、使用 SurfaceView
通過 SurfaceHolder 對象的 lockCanvas() 方法就可以獲得當前的 Canvas 繪圖對象哄尔。接下來就可以與在 View 中進行的繪制操作一樣進行繪制了。這里需要注意柠并,獲取到的 Canvas 對象還是繼續(xù)上次的 Canvas 對象岭接,而不是一個新的 Canvas 對象。因此臼予,之前的繪圖操作都會被保留鸣戴。如果需要擦除,則可以在繪制前粘拾,通過 drawColor() 方法來進行清屏操作窄锅。
繪制時,充分利用 SurfaceView 的三個回調方法缰雇,在 surfaceCreated() 方法里開啟子線程進行繪制入偷,而子線程使用一個 while (mIsDrawing) {} 的循環(huán)來不停地進行繪制,而在繪制的具體邏輯中械哟,通過 lockCanvas() 方法來獲取 Canvas 對象來繪制疏之,并通過 unlockCanvasAndPost(mCanvas) 方法對畫布內容進行提交。整個 SurfaceView 模板代碼如下:
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* Created by Deeson on 2017/5/23.
*/
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder mHolder;
//用于繪圖的canvas
private Canvas mCanvas;
//子線程標志位
private boolean mIsDrawing;
public MySurfaceView (Context context) {
super(context);
init();
}
public MySurfaceView (Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MySurfaceView (Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
this.setKeepScreenOn(true);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mIsDrawing = true;
new Thread(this).start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mIsDrawing = false;
}
@Override
public void run() {
while (mIsDrawing) {
draw();
}
}
private void draw() {
try {
mCanvas = mHolder.lockCanvas();
//draw something
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != mCanvas) {
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}
以上代碼基本可以滿足大部分 SurfaceView 的繪圖需求暇咆,唯一需要注意的是在繪制方法中体捏,將 mHolder.unlockCanvasAndPost(mCanvas);
方法放到 finally 代碼塊中,保證每次都能將內容提交糯崎。
最后
關于 SurfaceView 的實例演練几缭,可以看看我的這篇文章 Android:貝塞爾曲線原理分析
按照慣例,需要送上 demo 下載沃呢,如下 gif 所示: