Android中SurfaceView和普通view的區別及使用

1 SurfaceView介紹

在這裡插入圖片描述

SurfaceView第一印象它是一個view,因為它繼承瞭View,有兩個直接子類GLSurfaceView,VideoView。但根據SDK文檔SurfaceView和普通的view又有較大區別。

最顯著的區別就是普通view和它的宿主窗口共享一個繪圖表面(Surface),SurfaceView雖然也在View的樹形結構中,但是它有屬於自己的繪圖表面,Surface 內部持有一個Canvas,可以利用這個Canvas繪制。

SurfaceView提供一個直接的繪圖表面(Surface)嵌入到視圖結構層次中。你可以控制這個Surface的格式,大小,SurfaceView負責在屏幕上正確的擺放Surface。簡單說就是SurfaceView擁有自己的Surface,它與宿主窗口是分離的。
我們知道窗口中的view共享一個window,window又對應一個Surface,所以窗口中的view共享一個Surface,而SurfaceView擁有自己的Surface。SurfaceView會創建一個置於應用窗口之後的新窗口,SurfaceView相當於在Window上挖一個洞,它就是顯示在這個洞裡,其他的View是顯示在Window上,所以View可以顯示在 SurfaceView之上,也可以添加一些層在SurfaceView之上。

SurfaceView的窗口刷新的時候不需要重繪應用程序的窗口而android普通窗口的視圖繪制機制是一層一層的,任何一個子元素或者是局部的刷新都會導致整個視圖結構全部重繪一次。

對於普通的view,Android中的窗口界面包括多個View組成的View Hierachy的樹形結構,隻有最頂層的DecorView才對WMS可見,這個DecorView在WMS中有一個對應的WindowState,此時APP請求創建Surface時,會在SurfaceFlinger內部建立對應的Layer。而對於SurfaceView它自帶一個Surface,這個Surface在WMS有自己對應的WindowState,在SurfaceFlinger中有自己對應的layer。SurfaceView從APP端看它仍然在View hierachy結構中,但在WMS和SurfaceFlinger中它與宿主窗口是分離的。因此SurfaceView的Surface的渲染可以放到單獨線程去做,不會影響主線程對事件的響應。但因為這個Surface不在View hierachy中,它的顯示也不受View的屬性控制,所以不能進行平移,縮放等變換(對SurfaceView進行ScrollBy,ScrollTo操作沒有效果(還有透明度,旋轉),普通View進行平移操作,內部內容會移動,SurfaceView進行這些操作內部不會移動。如果對包裹它的ViewGroup進行平移旋轉等操作,也無法達到我們想要的效果。同時SurfaceView不能放在類似RecyclerView或ScrollView中,一些View中的特性也無法使用。

SurfaceView不支持平移,縮放,旋轉等動畫,但是當我們利用SurfaceView測試這些不支持的動畫時,如果使用的是7.0 甚至更高版本的Android系統,會發現SurfaceView也支持平移,縮放的動畫操作。

View和SurfaceView的區別:

View適用主動更新,SurfaceView 適用被動更新,如頻繁的刷新
View在UI線程更新,在非UI線程更新會報錯,當在主線程更新view時如果耗時過長也會出錯, SurfaceView在子線程刷新不會阻塞主線程,適用於界面頻繁更新、對幀率要求較高的情況。
SurfaceView可以控制刷新頻率。
SurfaceView底層利用雙緩存機制,繪圖時不會出現閃爍問題。

雙緩沖技術是遊戲開發中的一個重要的技術,主要是為瞭解決 反復局部刷屏帶來的閃爍。遊戲,視頻等畫面變化較頻繁,前面還沒有顯示完,程序又請求重新繪制,這樣屏幕就會不停地閃爍。雙緩沖技術會把要處理的圖片在內存中處理好之後,把要畫的東西先畫到一個內存區域裡,然後整體的一次性畫出來,將其顯示在屏幕上。

2 SurfaceView 使用步驟

使用SurfaceView的步驟:

首先要繼承SurfaceView,實現SurfaceHolder.Callback接口。

重寫方法:

  • surfaceChanged:surface大小或格式發生變化時觸發,在surfaceCreated調用後該函數至少會被調用一次。
  • surfaceCreated:Surface創建時觸發,一般在這個函數開啟繪圖線程(新的線程,不要再這個線程中繪制Surface)。
  • surfaceDestroyed:銷毀時觸發,一般不可見時就會銷毀。

利用getHolder()獲取SurfaceHolder對象,調用SurfaceHolder.addCallback添加回調

SurfaceHolder.lockCanvas 獲取Canvas對象並鎖定畫佈,調用Canvas繪圖,SurfaceHolder.unlockCanvasAndPost 結束鎖定畫佈,提交改變。

3 SurfaceHolder

SurfaceView的雙緩沖的機制非常消耗系統內存,Android規定SurfaceView不可見時,會立即銷毀SurfaceView的SurfaceHolder,以達到節約系統資源的目的,所以需要利用SurfaceHolder的回調函數對SurfaceHolder進行維護。
提供瞭三個回調函數讓我們知道SurfaceHolder的創建、銷毀或者改變
void surfaceDestroyed(SurfaceHolder holder):當SurfaceHolder被銷毀的時候回調。
void surfaceCreated(SurfaceHolder holder):當SurfaceHolder被創建的時候回調。
void surfaceChange(SurfaceHolder holder):當SurfaceHolder的尺寸或格式發生變化的時候被回調。

abstract Canvas lockCanvas():
獲取一個Canvas對象,並鎖定,得到的Canvas對象,就是Surface中一個成員。

** abstract Canvas lockCanvas(Rectdirty):**
僅僅鎖定dirty所指定的矩形區域。

abstract void unlockCanvasAndPost(Canvascanvas)
當改動Surface中的數據後,釋放同步鎖,並提交改變,然後將新的數據進行展示,同一時候Surface中相關數據會被丟失。

public abstract void setType (int type):
設置Surface的類型,高版本中,setType這種方法已經被depreciated瞭,系統會自動設置。

  • SURFACE_TYPE_NORMAL:用RAM緩存原生數據的普通Surface
  • SURFACE_TYPE_HARDWARE:適用於DMA(Direct memory access )引擎和硬件加速的
  • SurfaceSURFACE_TYPE_GPU:適用於GPU加速的Surface
  • SURFACE_TYPE_PUSH_BUFFERS:表明該Surface不包括原生數據,Surface用到的數據由其它對象提供,在Camera圖像預覽中就使用該類型的Surface,有Camera負責提供給預覽Surface數據,生成圖像更流暢。

兼容性:
SurfaceView的兼容性
  Android4.0以下SurfaceView不會自動維護緩沖區,播放視頻時,如果使用SurfaceView開發遊戲應用,就需要我們自己維護這個緩沖區瞭。

// 4.0版本之下需要設置的屬性
getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

4 SurfaceView的簡單使用

繪制圓形:

public class SurfaceViewDemo extends SurfaceView implements SurfaceHolder.Callback{
    private SurfaceHolder mSurfaceHolder;
    private Canvas mCanvas;
    private Paint paint;

    public SurfaceViewDemo(Context context) {
        this(context,null,0);
    }

    public SurfaceViewDemo(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public SurfaceViewDemo(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mSurfaceHolder = getHolder();
        mSurfaceHolder.addCallback(this);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.RED);
        paint.setStrokeWidth(5);
        paint.setStyle(Paint.Style.STROKE);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        System.out.println("=========surfaceCreated========");
        new Thread(new Runnable() {
            @Override
            public void run() {
                draw();
            }
        }).start();
    }

    private void draw() {
        try {
            System.out.println("============draw========");
            mCanvas = mSurfaceHolder.lockCanvas();
            mCanvas.drawCircle(500,500,300,paint);
            mCanvas.drawCircle(100,100,20,paint);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (mCanvas != null)
                mSurfaceHolder.unlockCanvasAndPost(mCanvas);
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        System.out.println("=========surfaceChanged========");
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        System.out.println("=========surfaceDestroyed========");
    }
}

在這裡插入圖片描述

回調函數的調用:
首次進入:
=surfaceCreated
=surfaceChanged
====draw
點擊Home鍵:
=surfaceDestroyed

再次回到原來頁面:
=surfaceCreated
=surfaceChanged
====draw

所以當SurfaceView不可見時會銷毀SurfaceHolder,再次進入會重新調用surfaceCreated生成新的SurfaceHolder。surfaceChanged函數在surfaceCreated調用後該函數至少會被調用一次。

SurfaceView播放視頻,不要忘記存儲權限
mediaPlayer.setDisplay(getHolder());
視頻資源為利用模擬器錄制的mp4視頻

public class SurfaceViewDemo2 extends SurfaceView implements SurfaceHolder.Callback{
    private SurfaceHolder mSurfaceHolder;
    private Canvas mCanvas;
    private Paint paint;
    private MediaPlayer mediaPlayer;

    public SurfaceViewDemo2(Context context) {
        this(context,null,0);
    }

    public SurfaceViewDemo2(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public SurfaceViewDemo2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mSurfaceHolder = getHolder();
        mSurfaceHolder.addCallback(this);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
        setZOrderOnTop(true);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.RED);
        paint.setStrokeWidth(5);
        paint.setStyle(Paint.Style.STROKE);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        System.out.println("=========surfaceCreated========");
        new Thread(new Runnable() {
            @Override
            public void run() {
                //draw();
              play();
            }
        }).start();
    }

    private void draw() {

        try {
            System.out.println("============draw========");
            mCanvas = mSurfaceHolder.lockCanvas();
            mCanvas.drawCircle(500,500,300,paint);
            mCanvas.drawCircle(100,100,20,paint);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (mCanvas != null)
                mSurfaceHolder.unlockCanvasAndPost(mCanvas);
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        System.out.println("=========surfaceChanged========");
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        System.out.println("=========surfaceDestroyed========");
        if (mediaPlayer != null ){
            stop();
        }
    }


    protected void stop() {
        if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            mediaPlayer.stop();
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }

    protected void play() {
        String path = "/sdcard/DCIM/Camera/VID_20190110_102218.mp4";
        File file = new File(path);
        if (!file.exists()) {
            return;
        }
            try {
                mediaPlayer = new MediaPlayer();
                mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
                // 設置播放的視頻源
                mediaPlayer.setDataSource(file.getAbsolutePath());
                // 設置顯示視頻的SurfaceHolder
                mediaPlayer.setDisplay(getHolder());

                mediaPlayer.prepareAsync();
                mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {

                    @Override
                    public void onPrepared(MediaPlayer mp) {
                        mediaPlayer.start();
                    }
                });
                mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {

                    @Override
                    public void onCompletion(MediaPlayer mp) {
                        replay();
                    }
                });

                mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {

                    @Override
                    public boolean onError(MediaPlayer mp, int what, int extra) {
                        play();
                        return false;
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
    }

    protected void replay() {
        if (mediaPlayer!=null){
         mediaPlayer.start();
        }else{
            play();
        }
    }

    protected void pause() {
        if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        }else{
            mediaPlayer.start();
        }
    }
}

圖片7

補充 對SurfaceView進行平移,旋轉等操作

添加 mSurfaceView.scrollBy(10,10);
效果如下:完全沒有效果

在這裡插入圖片描述

對包裹它的viewgroup添加縮放動畫。

mContainer.animate().scaleX(0.4f).scaleY(0.7f);

在這裡插入圖片描述

此時拍攝出來的照片為:

在這裡插入圖片描述

拍攝出來的照片完全沒有受縮放的影響。

到此這篇關於Android中SurfaceView和普通view的區別及使用的文章就介紹到這瞭,更多相關Android中SurfaceView和普通view內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: