Android:SurfaceView 的使用(附代碼模板)

前言

摘自《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 所示:

mySurfaceView.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容