前言
摘自《Android群英傳》
Android提供了View進(jìn)行繪圖處理,View可以滿足大部分的繪圖需求,但在某些時(shí)候也會(huì)心有余而力不足。我們知道,View通過刷新來重繪視圖,Android 系統(tǒng)通過發(fā)出VSYNC信號(hào)來進(jìn)行屏幕的重繪,刷新的時(shí)間間隔為16ms。如果在16ms內(nèi)View完成了你所需要執(zhí)行的所有操作,那么用戶在視覺上就不會(huì)產(chǎn)生卡頓的感覺;而如果執(zhí)行的操作邏輯太多,特別是需要頻繁刷新的界面上,例如游戲界面,就會(huì)不斷阻塞主線程,從而導(dǎo)致畫面卡頓。很多情況下,在自定義View的log中會(huì)看到如下的警告:
“Skipped 47 frames! The application may be doing too much work on its main thread.”
為了避免這一問題的產(chǎn)生,Android系統(tǒng)提供了SurfaceView組件。
View 和 SurfaceView 的區(qū)別
- View 主要適用于主動(dòng)更新的情況下,而 SurfaceView 主要適用于被動(dòng)更新,例如頻繁地刷新。
- View 在主線程中對(duì)畫面進(jìn)行刷新,而 SurfaceView 通常會(huì)通過一個(gè)子線程來進(jìn)行頁面的刷新。
- View 在繪圖時(shí)沒有使用雙緩沖機(jī)制,而 SurfaceView 在底層實(shí)現(xiàn)機(jī)制中就已經(jīng)實(shí)現(xiàn)了雙緩沖機(jī)制。
總結(jié)就是,如果你的自定義View需要頻繁刷新,或者刷新時(shí)數(shù)據(jù)處理量比較大,那么你就可以考慮使用 SurfaceView 來取代 View 了。
SurfaceView 的使用
SurfaceView 的使用雖然比 View 復(fù)雜,但是 SurfaceView 在使用時(shí),有一套使用的模板代碼,大部分的 SurfaceView 繪圖操作都可以套用這樣的模板代碼來進(jìn)行編寫。因此 SurfaceView 的使用會(huì)更加簡(jiǎn)單。
創(chuàng)建一個(gè) SurfaceView 的模板
1、 創(chuàng)建 SurfaceView
創(chuàng)建自定義的 MySurfaceView 繼承自 SurfaceView ,并實(shí)現(xiàn)兩個(gè)接口——SurfaceHolder.Callback, Runnable,同時(shí)實(shí)現(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 的構(gòu)造方法中,需要對(duì) SurfaceView 進(jìn)行初始化。通常需要定義以下三個(gè)成員變量,如下:
private SurfaceHolder mHolder;
//用于繪圖的canvas
private Canvas mCanvas;
//子線程標(biāo)志位
private boolean mIsDrawing;
初始化方法就是初始化一個(gè) SurfaceHolder 對(duì)象并注冊(cè) SurfaceHolder 的回調(diào)方法,如下:
mHolder = getHolder();
mHolder.addCallback(this);
另外兩個(gè)成員變量——Canvas 和標(biāo)志位。使用 Canvas 來進(jìn)行繪圖;使用標(biāo)志位來控制之前提到的用于繪制的子線程。
3、使用 SurfaceView
通過 SurfaceHolder 對(duì)象的 lockCanvas() 方法就可以獲得當(dāng)前的 Canvas 繪圖對(duì)象。接下來就可以與在 View 中進(jìn)行的繪制操作一樣進(jìn)行繪制了。這里需要注意,獲取到的 Canvas 對(duì)象還是繼續(xù)上次的 Canvas 對(duì)象,而不是一個(gè)新的 Canvas 對(duì)象。因此,之前的繪圖操作都會(huì)被保留。如果需要擦除,則可以在繪制前,通過 drawColor() 方法來進(jìn)行清屏操作。
繪制時(shí),充分利用 SurfaceView 的三個(gè)回調(diào)方法,在 surfaceCreated() 方法里開啟子線程進(jìn)行繪制,而子線程使用一個(gè) while (mIsDrawing) {} 的循環(huán)來不停地進(jìn)行繪制,而在繪制的具體邏輯中,通過 lockCanvas() 方法來獲取 Canvas 對(duì)象來繪制,并通過 unlockCanvasAndPost(mCanvas) 方法對(duì)畫布內(nèi)容進(jìn)行提交。整個(gè) 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;
//子線程標(biāo)志位
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 代碼塊中,保證每次都能將內(nèi)容提交。
最后
關(guān)于 SurfaceView 的實(shí)例演練,可以看看我的這篇文章 Android:貝塞爾曲線原理分析
按照慣例,需要送上 demo 下載,如下 gif 所示:
