2015年11月30日 星期一

SurfaceView

SurfaceView 介面最適合開發顯示速率較快的應用程式。
一張靜態的圖是看不出 SurfaceView 的能力的, 如果要讓圖 "動"起來, 則還需要配合執行緒的運用, 
透過執行緒不斷去改變圖片的座標並反覆貼上圖片, 就可以達到動態的效果。

Surface、SurfaceView、SurfaceHolder及SurfaceHolder.Callback之间的关系:
1. Surface:
    屏幕显示内容合成器(screen compositor)所管理的原生缓冲器。
    用来画图形(graphics)或图像(image)的地方。
    常画图是在一个Canvas对象上面进行。
2. SurfaceView:
    通过SurfaceView就可以看到Surface的部分或者全部的内容
    
     
3. SurfaceHolder:
    一个接口,其作用就像一个关于Surface的监听器。
    提供访问和控制SurfaceView背后的Surface 相关的方法
    通过三个回调方法,让我们可以感知到Surface的创建 surfaceCreated、销毁 surfaceDestroyed 或者改变 surfaceChanged 

參考資料:



在 canvas 畫布上貼圖的三個步驟:
1. 鎖住畫布 : canvas = holder.lockCanvas(); 
2. 在畫布上貼圖 : canvas.drawBitmap(bp,x,y,null);
3. 解鎖並po出畫布 : holder.unlockCanvasAndPost(canvas);

SurfaceHolder.Callback 相關聯的三個程序 :
public void surfaceCreated(...) -- surface 建立時
public void surfaceChanged(...) -- surface 改變時
public void surfaceDestroyed(...) -- surface 結束時


參考資料:



用 SurfaceView 與 View 繪圖的差異性:
1. SurfaceView :是在一個新起的單獨執行緒中可以重新繪製畫面
     - 主動更新。比如一個人在一直跑動。這就需要一個單獨的 Thread 不停的重繪人的狀態,避免阻塞主 UI Thread。 (如: 拉霸台 App, 太陽系 App)
2. View :必須在 UI 的主執行緒中更新畫面。
     - 被動更新畫面的。比如棋類,這種用 View 就好了。
     - 因為畫面的更新是賴於 onTouch 來更新,可以直接使用 invalidate。因為這種情況下,這一次 Touch 和下一次的 Touch 需要的時間比較長些,不會產生影響。           (如: 繪圖 App)

在 UI 的主執行緒中更新畫面,可能會引發問題,比如你更新畫面的時間過長,那麼你的主 UI 執行緒會被你正在畫的函數阻塞,那麼將無法回應按鍵,觸屏等消息。
SurfaceView 具有雙緩衝機制。因此,開發遊戲時儘量使用 SurfaceView 而不要使用 View,這樣的話效率較高,而且 SurfaceView 的功能也更加完善。


參考資料:



SurfaceView 刷新特定區域:
內部矩形:比如surfaceview上绘制旋转图片的时候,只刷新一个特定的矩形区域就可以了。
在 "鎖住畫布" 步驟中,加入要刷新的矩形區域:
canvas = holder.lockCanvas(new Rect(0, 0, 130, 130)); //获取画布 像这种带rect参数的就是脏矩形的刷新。


參考資料:






----------------------------------------------------------------
1. 新增 自訂義 SurfaceView Class 到 Layout :
----------------------------------------------------------------

 - 在 Layout 中加入 LinearLayout

<LinearLayout    android:orientation="vertical"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:id="@+id/sfviewLLayout"/>

 - 程式碼部分:
private LinearLayout sfviewLayout;private tw.com.manufacturer.MFView mfView;
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);     sfviewLayout = (LinearLayout)findViewById(R.id.sfviewLLayout);     mfView = new MFView(this);     sfviewLayout.addView(mfView);
}







----------------------------------------------------------------
2. 自訂義 SurfaceView Class :
---------------------------------------------------------------- 

SurfaceView 需要一個 SurfaceHolder  來抓取與設定 surface States、一個 DrawThread 繪圖用的執行緒 來執行 DoDraw 方法。


public class MFView extends SurfaceView implements SurfaceHolder.Callback{
    private SurfaceHolder holder = null;
    public DrawThread dt = null;    
    public MFView(Context context){
        super(context);        init();
    }
    public MFView(Context context, AttributeSet attrs) {
        super(context, attrs); // 回來執行父類別
        init();
    }
    private void init(){
        setFocusable(true); // make sure we get key events
        holder = this.getHolder();
        holder.addCallback(this);
    }

    // Draw Thread 呼叫此方法來繪圖
    public void DoDraw(Canvas canvas) {
        canvas.save();

        canvas.drawCircle
        canvas.drawRect
        ...

        canvas.restore();
    }
    //------------------    // 觸碰事件判斷和處理    //------------------
    public boolean onTouchEvent(MotionEvent Event) {
        switch (Event.getAction() & MotionEvent.ACTION_MASK){
            case MotionEvent.ACTION_DOWN :
                break;            case MotionEvent.ACTION_POINTER_DOWN:
                break;            case MotionEvent.ACTION_MOVE :
                break;            case MotionEvent.ACTION_POINTER_UP:
                break;            case MotionEvent.ACTION_UP:
                break;            default :
                break;        }
        return true;    }
    //------------------------------------------------------    // SurfaceView 狀態方法(Created, Changed, Destroyed    //------------------------------------------------------
    @Override
    public synchronized void surfaceCreated(SurfaceHolder holder) {
    }
    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }
    @Override    public void surfaceDestroyed(SurfaceHolder holder) {
        // thread 停止 runnable 執行內容        boolean retry = true;       dt.setRunning(false);       while (retry) {
           try {
                dt.join();                retry = false;           } catch (InterruptedException e) {}
        }
    }
}






----------------------------------------------------------------
3. 自訂義 Thread Class :
---------------------------------------------------------------- 

public class DrawThread extends Thread {
    private MFView mfv;                     //物件物件變數    private SurfaceHolder surfaceHolder;    //SurfaceHolder物件物件變數    private boolean flag=false;              //線程執行旗標位    private int sleepSpan = 1;           //休眠時間    private Canvas canvas = new Canvas();   //宣告一個Canvas物件
    //建構式    public DrawThread(MFView mfv,SurfaceHolder surfaceHolder){
       this.mfv = mfv;                       //BallView物件應用設值       this.surfaceHolder = surfaceHolder;    //SurfaceHolder物件應用設值    }

    public void setRunning(boolean runflag){
       this.flag = runflag;                //設置旗標位    }
    public void run(){
       while(flag){
          try{
             Thread.sleep(sleepSpan);                     //線程休眠一段時間                canvas = surfaceHolder.lockCanvas();           //取得BallView的畫布                 synchronized(surfaceHolder){
                mfv.DoDraw(canvas);                          //呼叫BallViewdoDraw方法進行繪製                 }
          } catch(Exception e){
              e.printStackTrace();                        //捕獲並打印異常            } finally{
             if(canvas != null){                              //如果canvas不為空                surfaceHolder.unlockCanvasAndPost(canvas); //surfaceHolder解鎖並將畫布物件傳回                }
         }     
       }
    }
}







----------------------------------------------------------------
1. 範例練習1:
----------------------------------------------------------------

 - 目標:
在一個有背景的SurfaceView操作一顆足球左右跑。
程式中共分為四個部分:
1. OlaTeach_game1 extends Activity:作為程式啟動的起點
2. OlaView extends SurfaceView:註冊繪圖物件,顯示繪圖的成果
3. OlaDrawThread extends Thread:負責繪圖的Thread
4. OlaBall extends Thread:負責當好一顆球(計算球的運動方式)

 - 架構:
 - 流程:
.1. 程式啟動,OlaTeach_game1觸發onCreate。
.2. 建立OlaView(new),觸發OlaView的建構式,一併由該建構式建立OlaDrawThread與OlaBall兩個Thread。.
3. SetContentView觸發 OlaView的surfaceCreated(),並由該函式啟動OlaDrawThread。
.4. 執行OlaView的startMove(),並由該函式啟動OlaBall。
.5. 由OlaDrawThread這個Thread執行OlaView的DoDraw來不斷重新繪製OlaView。.
6. 啟動OlaBall後,由該Thread不斷計算球最後的落點(X,Y)。
.7. 由DoDraw來觸發OlaBall的drawSelf來繪製球。

 - 重要程式碼:
.1. 繪製SurfaceView(必須先取得View的getHolder,在繪圖之前先上鎖,畫完以後再解鎖).
OlaView內:
getHolder().addCallback(this);//新增Callback介面.OlaDrawThread dt = new OlaDrawThread(this,getHolder());//新建後臺更新螢幕執行緒

OlaDrawThread內:
SurfaceHolder surfaceHolder = surfaceHolder; //利用建構式接surfaceHolder.canvas = surfaceHolder.lockCanvas(null); //為畫布加鎖.OlaView.doDraw(canvas); //重繪畫布.surfaceHolder.unlockCanvasAndPost(canvas); //解鎖畫布

目標是要在畫面上繪製一個會動的球,所以準備了一個放畫布的架子(Activity),一個畫布(OlaView),一個畫畫的人(OlaDrawThread),一個球(OlaBall)。


參考資料:

沒有留言:

張貼留言