天天看點

SurfaceView,SurfaceHolder,SurfaceHolder.CallBackandroid大掃盲之SurfaceView,SurfaceHolder,SurfaceHolder.CallBack.你hold住了嗎

SurfaceView 教程

SurfaceView介紹       通常情況程式的 View和使用者響應都是在同一個線程中處理的,這也是為什麼處理長時間事件(例如通路網絡)需要放到另外的線程中去(防止阻塞目前UI線程的操作和繪制)。但是在其他線程中卻不能修改UI元素,例如用背景線程更新自定義View(調用View的在自定義View中的onDraw函數)是不允許的。          如果需要在另外的線程繪制界面、需要迅速的更新界面或則渲染UI界面需要較長的時間,這種情況就要使用SurfaceView了。SurfaceView中包含一個Surface對象,而Surface是可以在背景線程中繪制的。Surface屬于OPhone底層顯示系統,關于這方面的介紹請參考附錄中的資料 [1]。 SurfaceView的性質決定了其比較适合一些場景:需要界面迅速更新、對幀率要求較高的情況。使用SurfaceView需要注意以下幾點情況:

  • SurfaceView和SurfaceHolder.Callback函數都從目前SurfaceView視窗線程中調用(一般而言就是程式的主線程)。有關資源狀态要注意和繪制線程之間的同步。
  •  在繪制線程中必須先合法的擷取Surface才能開始繪制内容,在SurfaceHolder.Callback.surfaceCreated() 和SurfaceHolder.Callback.surfaceDestroyed()之間的狀态為合法的,另外在Surface類型為SURFACE_TYPE_PUSH_BUFFERS時候是不合法的。
  •  額外的繪制線程會消耗系統的資源,在使用SurfaceView的時候要注意這點。

  使用SurfaceView          隻要繼承SurfaceView類并實作SurfaceHolder.Callback接口就可以實作一個自定義的SurfaceView了,SurfaceHolder.Callback在底層的Surface狀态發生變化的時候通知View,SurfaceHolder.Callback具有如下的接口:

  •  surfaceCreated(SurfaceHolder holder):當Surface第一次建立後會立即調用該函數。程式可以在該函數中做些和繪制界面相關的初始化工作,一般情況下都是在另外的線程來繪制界面,是以不要在這個函數中繪制Surface。
  •  surfaceChanged(SurfaceHolder holder, int format, int width,int height):當Surface的狀态(大小和格式)發生變化的時候會調用該函數,在surfaceCreated調用後該函數至少會被調用一次。
  •  surfaceDestroyed(SurfaceHolder holder):當Surface被摧毀前會調用該函數,該函數被調用後就不能繼續使用Surface了,一般在該函數中來清理使用的資源。

         通過SurfaceView的getHolder()函數可以擷取SurfaceHolder對象,Surface 就在SurfaceHolder對象内。雖然Surface儲存了目前視窗的像素資料,但是在使用過程中是不直接和Surface打交道的,由SurfaceHolder的Canvas lockCanvas()或則Canvas lockCanvas(Rect dirty)函數來擷取Canvas對象,通過在Canvas上繪制内容來修改Surface中的資料。如果Surface不可編輯或則尚未建立調用該函數會傳回null,在 unlockCanvas() 和 lockCanvas()中Surface的内容是不緩存的,是以需要完全重繪Surface的内容,為了提高效率隻重繪變化的部分則可以調用lockCanvas(Rect dirty)函數來指定一個dirty區域,這樣該區域外的内容會緩存起來。在調用lockCanvas函數擷取Canvas後,SurfaceView會擷取Surface的一個同步鎖直到調用unlockCanvasAndPost(Canvas canvas)函數才釋放該鎖,這裡的同步機制保證在Surface繪制過程中不會被改變(被摧毀、修改)。 當在Canvas中繪制完成後,調用函數unlockCanvasAndPost(Canvas canvas)來通知系統Surface已經繪制完成,這樣系統會把繪制完的内容顯示出來。為了充分利用不同平台的資源,發揮平台的最優效果可以通過SurfaceHolder的setType函數來設定繪制的類型,目前接收如下的參數:

  • SURFACE_TYPE_NORMAL:用RAM緩存原生資料的普通Surface
  • SURFACE_TYPE_HARDWARE:适用于DMA(Direct memory access )引擎和硬體加速的Surface
  • SURFACE_TYPE_GPU:适用于GPU加速的Surface
  • SURFACE_TYPE_PUSH_BUFFERS:表明該Surface不包含原生資料,Surface用到的資料由其他對象提供,在Camera圖像預覽中就使用該類型的Surface,有Camera負責提供給預覽Surface資料,這樣圖像預覽會比較流暢。如果設定這種類型則就不能調用lockCanvas來擷取Canvas對象了。

           目前OPhone還不支援GIF動畫圖檔的顯示,這裡就通過一個SurfaceView來展示如何定制一個支援GIF動畫的View,同時從該示例(注釋)中也可以看出如何使用SurfaceView。          首先建立一個GifView繼承在SurfaceView,代碼如下:

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

android大掃盲之SurfaceView,SurfaceHolder,SurfaceHolder.CallBack.

你hold住了嗎

最近接觸到了SurfaceView,SurfaceHolder,SurfaceHolder.CallBack,一直不求其解,現在來粗淺認識一下它們。

先看一下官方的定義:

1.SurfaceView

SurfaceView是視圖(View)的繼承類,這個視圖裡内嵌了一個專門用于繪制的Surface。你可以控制這個Surface的格式和尺寸。Surfaceview控制這個Surface的繪制位置。

surface是縱深排序(Z-ordered)的,這表明它總在自己所在視窗的後面。surfaceview提供了一個可見區域,隻有在這個可見區域内 的surface部分内容才可見,可見區域外的部分不可見。surface的排版顯示受到視圖層級關系的影響,它的兄弟視圖結點會在頂端顯示。這意味者 surface的内容會被它的兄弟視圖遮擋,這一特性可以用來放置遮蓋物(overlays)(例如,文本和按鈕等控件)。注意,如果surface上面 有透明控件,那麼它的每次變化都會引起架構重新計算它和頂層控件的透明效果,這會影響性能。

  你可以通過SurfaceHolder接口通路這個Surface.用getHolder()方法可以得到這個接口。

surfaceview變得可見時,surface被建立;surfaceview隐藏前,surface被銷毀。這樣能節省資源。如果你要檢視 surface被建立和銷毀的時機,可以重載surfaceCreated(SurfaceHolder)和 surfaceDestroyed(SurfaceHolder)。

surfaceview的核心在于提供了兩個線程:UI線程和渲染線程。這裡應注意:

1> 所有SurfaceView和SurfaceHolder.Callback的方法都應該在UI線程裡調用,一般來說就是應用程式主線程。渲染線程所要通路的各種變量應該作同步處理。

2> 由于surface可能被銷毀,它隻在SurfaceHolder.Callback.surfaceCreated()和 SurfaceHolder.Callback.surfaceDestroyed()之間有效,是以要確定渲染線程通路的是合法有效的surface。

2.SurfaceHolder

顯示一個surface的抽象接口,使你可以控制surface的大小和格式, 以及在surface上編輯像素,和監視surace的改變。這個接口通常通過SurfaceView類實作。

3. SurfaceHolder.Callback

使用者可以實作此接口接收surface變化的消息。當用在一個SurfaceView 中時, 它隻在SurfaceHolder.Callback.surfaceCreated()和SurfaceHolder.Callback.surfaceDestroyed()之間有效。設定Callback的方法是SurfaceHolder.addCallback.

看完官方的介紹,明白了許多,surfaceView帶z-order的,還可以在上面操作像素有木有,還有單獨的渲染線程哦親。如果做遊戲開發,當然選SurfaceView了。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

android中用SurfaceHolder處理SurfaceView的畫圖

在用SurfaceView進行遊戲開發過程中,用到SurfaceHolder來處理它的Canvas上畫的效果和動畫是必不可少的。用于控制表面,大小,像素等。

Abstract interface to someone holding a display surface. Allows you to control the surface size and format,

edit the pixels in the surface, and monitor changes to the surface. This interface is typically available

through the SurfaceView class.

其中特别要注意以下的幾個函數:

abstract void addCallback(SurfaceHolder.Callback callback);

// 給SurfaceView目前的持有者一個回調對象。

abstract Canvas lockCanvas();

// 鎖定畫布,一般在鎖定後就可以通過其傳回的畫布對象Canvas,在其上面畫圖等操作了。

abstract Canvas lockCanvas(Rect dirty);

// 鎖定畫布的某個區域進行畫圖等..因為畫完圖後,會調用下面的unlockCanvasAndPost來改變顯示内容。

// 相對部分記憶體要求比較高的遊戲來說,可以不用重畫dirty外的其它區域的像素,可以提高速度。

abstract void unlockCanvasAndPost(Canvas canvas);

// 結束鎖定畫圖,并送出改變。

例子:

// 請參考上一篇文章:android中用SurfaceView做遊戲開發
    class DrawThread extends Thread {
        private SurfaceHolder holder;
        private boolean running = true;
        protected DrawThread(SurfaceHolder holder) {this.holder = holder;}
        protected void doStop() { running = false; }
        public void run() {
            Canvas c = null;
            while( running ) {
                c = holder.lockCanvas(null);
                // 鎖定整個畫布,在記憶體要求比較高的情況下,建議參數不要為null
                try {
                    synchronized(holder) {
                        bGrid.drawGrid(c);//畫遊戲中的網格
                        BBoom.drawBooms(c, booms); //畫遊戲中的炸彈
                        bFairy.drawFairy(c);//畫遊戲中的主角
                        // 畫的内容是z軸的,後畫的會覆寫前面畫的。
                    }
                } catch(Exception ex) {}
                finally {
                    holder.unlockCanvasAndPost(c);
                    //更新螢幕顯示内容
                }
    
            }
        }
    };

在android中開發遊戲,一般來說,或想寫一個複雜一點的遊戲,是必須用到SurfaceView來開發的。
經過這一陣子對android的學習,我找到了自已在android中遊戲開發的誤區,不要老想着用Layout和view去實作,不要将某個遊戲
中的對象做成一個元件來處理。應該盡量想着在Canvas(畫布)中畫出遊戲戲中的背景、人物、動畫等...
SurfaceView提供直接通路一個可畫圖的界面,可以控制在界面頂部的子視圖層。SurfaceView是提供給需要直接畫像素而不是
使用窗體部件的應用使用的。Android圖形系統中一個重要的概念和線索是surface。View及其子類(如TextView, Button)
要畫在surface上。每個surface建立一個Canvas對象(但屬性時常改變),用來管理view在surface上的繪圖操作,如畫點畫線。
還要注意的是,使用它的時候,一般都是出現在最頂層的:The view hierarchy will take care of correctly compositing 
with the Surface any siblings of the SurfaceView that would normally appear on top of it.

使用的SurfaceView的時候,一般情況下還要對其進行建立,銷毀,改變時的情況進行監視,這就要用到SurfaceHolder.Callback.
class BBatt extends SurfaceView implements SurfaceHolder.Callback {
    public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}
//看其名知其義,在surface的大小發生改變時激發
    public void surfaceCreated(SurfaceHolder holder){}
//同上,在建立時激發,一般在這裡調用畫圖的線程。
    public void surfaceDestroyed(SurfaceHolder holder) {}
//同上,銷毀時激發,一般在這裡将畫圖的線程停止、釋放。
}

例子:
public class BBatt extends SurfaceView implements 
                 SurfaceHolder.Callback, OnKeyListener {
             private BFairy bFairy;
             private DrawThread drawThread;
             public BBatt(Context context) {
                 super(context);
                 this.setLayoutParams(
                     new ViewGroup.LayoutParams(
                         Global.battlefieldWidth, Global.battlefieldHeight));
                 this.getHolder().addCallback( this );
                 this.setFocusable( true );
                 this.setOnKeyListener( this );
                 bFairy = new BFairy(this.getContext());
             }
             public void surfaceChanged(SurfaceHolder holder,
                  int format,int width,int height) {
                  drawThread = new DrawThread(holder);
                  drawThread.start();
             }
             public void surfaceDestroyed(SurfaceHolder holder) {
                  if( drawThread != null ) {
                        drawThread.doStop();
                        while (true) try {
                             drawThread.join();
                             break ;
                        } catch(Exception ex) {}
                  }
             }
             public boolean onKey(View view, int keyCode, KeyEvent event) {}
}      

繼續閱讀