Android中SurfaceView簡單使用

1.什麼是SurfaceView?

    表面意爲表層,表面,顧名思義SurfaceView就是指一個在表層的視圖對象。爲什麼說是在表層呢,這是因爲它有點特殊跟其他搜索不一樣,其他視圖是繪製在“表層”的上面,而它就是充當“表層”本身.SDK的文檔說到:SurfaceView就是在窗口上挖一個洞,它就是顯示在這個洞裏,所以的視圖是顯示在窗口上,所以查看可以顯式在SurfaceView之上,你也可以添加一些層在SurfaceView之上。從API中可以看出SurfaceView屬於View的子類它是專門爲製作遊戲而產生的,它的功能非常強大,最重要的是它支持OpenGL ES庫,2D和創建SurfaceView的時候需要實現SurfaceHolder.Callback接口,它可以用來監聽SurfaceView的狀態,比如:SurfaceView的改變,SurfaceView的創建,SurfaceView銷燬等,我們可以在相應的方法中做一些比如初始化的操作或者清空的操作等等。

Android的系統提供了視圖進行繪圖處理,我們通過自定義的視圖可以滿足大部分的繪圖需求,但是這有個問題就是我們通常自定義的視圖是用於主動更新情況的,用戶無法控制其繪製的速度,由於視圖是通過無效方法通知系統去調用view.onDraw方法進行重繪,而安卓系統是通過發出VSYNC信號來進行屏幕的重繪,刷新的時間是16毫秒,如果在16毫秒內搜索完成不了執行的操作,用戶就會看着卡頓,比如當繪製方法裏執行的邏輯過多,需要頻繁刷新的界面上,例如遊戲界面,那麼就會不斷的阻塞主線程,從而導致畫面卡頓。而SurfaceView相當於是另一個繪圖線程,它是不會阻礙主線程,並且它在底層實現機制中實現了雙緩衝機制。

2.如何使用SurfaceView?
        首先SurfaceView也是一個View,它也有自己的生命週期。因爲它需要另外一個線程來執行繪製操作,所以我們可以在它生命週期的初始化階段開闢一個新線程,然後開始執行繪製,當生命週期的結束階段我們插入結束繪製線程的操作。這些是由其內部一個SurfaceHolder對象完成的。  

SurfaceView它的繪製原理是繪製前先鎖定畫布(獲取畫布),然後等都繪製結束以後在對畫布進行解鎖,最後在把畫布內容顯示到屏幕上。       

通常情況下,使用以下步驟來創建一個SurfaceView的模板:

(1)創建SurfaceView

創建自定義的SurfaceView繼承自SurfaceView,並實現兩個接口:SurfaceHolder.Callback和Runnable。代碼如下:


public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable

通過實現這兩個接口,就需要在自定義的SurfaceView中實現接口的方法,對於SurfaceHolder.Callback方法,需要實現如下方法,其實就是SurfaceView的生命週期:

   @Override
    public void surfaceCreated(SurfaceHolder holder) {
      
 
    }
   @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
 
    }
 
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
   
    }

對於Runnable接口,需要實現run()方法,.

 @Override
    public void run() {
 
}

(2)初始化SurfaceView

在自定義的MySurfaceView的構造方法中,需要對SurfaceView進行初始化,包括SurfaceHolder的初始化、畫筆的初始化等。在自定義的SurfaceView中,通常需要定義以下三個成員變量:

    private SurfaceHolder mHolder;
    private Canvas mCanvas;//繪圖的畫布
    private boolean mIsDrawing;//控制繪畫線程的標誌位

 SurfaceHolder,顧名思義,它裏面保存了一個對Surface對象的引用,而我們執行繪製方法本質上就是操控Surface。SurfaceHolder因爲保存了對Surface的引用,所以使用它來處理Surface的生命週期。(說到底 SurfaceView的生命週期其實就是Surface的生命週期)例如使用 SurfaceHolder來處理生命週期的初始化。

(3)使用SurfaceView
通過SurfaceHolder對象的lockCanvans()方法,我們可以獲取當前的Canvas繪圖對象。接下來的操作就和自定義View中的繪圖操作一樣了。需要注意的是這裏獲取到的Canvas對象還是繼續上次的Canvas對象,而不是一個新的對象。因此,之前的繪圖操作都會被保留,如果需要擦除,則可以在繪製前,通過drawColor()方法來進行清屏操作。

繪製的時候,在surfaceCreated()方法中開啓子線程進行繪製,而子線程使用一個while(mIsDrawing)的循環來不停的進行繪製,在繪製的邏輯中通過lockCanvas()方法獲取Canvas對象進行繪製,通過unlockCanvasAndPost(mCanvas)方法對畫布內容進行提交。整體代碼模板如下:

public class MyView extends SurfaceView implements SurfaceHolder.Callback {
    private Paint paint;//聲明一個畫筆
    public MyView(Context context) {
        super(context);
        paint=new Paint();//實例化畫筆對象
        paint.setColor(Color.RED);

        // 爲SurfaceHolder添加一個SurfaceHolder.Callback回調接口
        getHolder().addCallback(this);
    }

    public void draw() {

        Canvas canvas=getHolder().lockCanvas();//圖形繪製之前鎖定畫布
        canvas.drawColor(Color.WHITE);//畫布爲白色
        canvas.save();//保存畫布的狀態
        canvas.drawRect(getWidth()/2,getHeight()/2,100,100,paint);
        //getWidth():獲取view的寬度   drawRect畫矩形
        canvas.restore();//重新使用畫布
        canvas.rotate(90,getWidth()/2,getHeight()/2);//將線繞view中心旋轉90度
        canvas.drawLine(0,getHeight()/2,getWidth(),getHeight(),paint);//畫線
        getHolder().unlockCanvasAndPost(canvas);//圖形繪製完之後解鎖畫布

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        draw();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }
}

這裏說一個優化的地方,這就是在run方法中。
在我們的draw()方法每一次更新所耗費的時間是不確定的。舉個例子 比如第一次循環draw() 耗費了1000毫秒 ,第二次循環draw() 耗時2000毫秒。很明顯這樣就會造成運行刷新時間時快時慢,可能出現卡頓現象。爲此最好保證每次刷新的時間是相同的,這樣可以保證整體畫面過渡流暢。

 	/**每30幀刷新一次屏幕**/
        public static final int TIME_IN_FRAME = 30;
	@Override
	public void run() {
	    while (mIsRunning) {
 
		/**取得更新之前的時間**/
		long startTime = System.currentTimeMillis();
 
		/**在這裏加上線程安全鎖**/
		synchronized (mSurfaceHolder) {
		    /**拿到當前畫布 然後鎖定**/
		    mCanvas =mSurfaceHolder.lockCanvas();
		    draw();
		    /**繪製結束後解鎖顯示在屏幕上**/
		    mSurfaceHolder.unlockCanvasAndPost(mCanvas);
		}
 
		/**取得更新結束的時間**/
		long endTime = System.currentTimeMillis();
 
		/**計算出一次更新的毫秒數**/
		int diffTime  = (int)(endTime - startTime);
 
		/**確保每次更新時間爲30幀**/
		while(diffTime <=TIME_IN_FRAME) {
		    diffTime = (int)(System.currentTimeMillis() - startTime);
		    /**線程等待**/
		    Thread.yield();
		}
 
	    }
	}

這裏說一下Thread.yield(): 與Thread.sleep(long millis):的區別:
Thread.yield(): 是暫停當前正在執行的線程對象 ,並去執行其他線程。

Thread.sleep(long millis):則是使當前線程暫停參數中所指定的毫秒數然後在繼續執行線程。

3.SurfaceView的使用實例
(1)正弦曲線

要繪製一個正弦曲線,只需要不斷修改橫縱座標的值,並讓他們滿足正弦函數即可。因此,我們需要一個Path對象來保存正弦函數上的座標點,在子線程的while循環中,不斷改變橫縱座標值。代碼如下:

public static final int TIME_IN_FRAME = 30;
    @Override
    public void run() {
        long startTime = System.currentTimeMillis();
         while(mIsDrawing){
             draw();
//             x+=1;
//             y=(int)(100*Math.sin(x*2*Math.PI/180)+400);
//             mPath.lineTo(x,y);
         }
        
        /**取得更新結束的時間**/
        long endTime = System.currentTimeMillis();
 
        /**計算出一次更新的毫秒數**/
        int diffTime  = (int)(endTime - startTime);
 
        /**確保每次更新時間爲30幀**/
        while(diffTime <=TIME_IN_FRAME) {
            diffTime = (int)(System.currentTimeMillis() - startTime);
            /**線程等待**/
            Thread.yield();
        }
    }

(2)畫圖板

我們也可以通過使用SurfaceView來實現一個簡單的繪圖板,繪圖的方法與View中進行繪圖所使用的方法一樣,也是通過Path對象記錄手指滑動的路徑來進行繪圖。在SurfaceView的onTouchEvent()方法中記錄Path路徑,代碼如下所示:

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x=(int)event.getX();
        int y=(int)event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mPath.moveTo(x,y);
                break;
            case MotionEvent.ACTION_MOVE:
                mPath.lineTo(x,y);
                break;
            case MotionEvent.ACTION_UP:
                break;
 
        }
        return true;//表示此View攔截處理觸摸事件
    }

並在draw方法中進行繪製:

 private void draw() {
        try{
            mCanvas=mHolder.lockCanvas();//獲取Canvas對象進行繪製
            //SurfaceView背景
            mCanvas.drawColor(Color.WHITE);
            mCanvas.drawPath(mPath,mPaint);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (mCanvas!=null){
                mHolder.unlockCanvasAndPost(mCanvas);//保證繪製的畫布內容提交
            }
        }
    }

4.SurfaceView和View的區別
總的歸納起來SurfaceView和View不同之處有:

1. SurfaceView允許其他線程更新視圖對象(執行繪製方法)而View不允許這麼做,它只允許UI線程更新視圖對象。

2. SurfaceView是放在其他最底層的視圖層次中,所有其他視圖層都在它上面,所以在它之上可以添加一些層,而且它不能是透明的。

3. 它執行動畫的效率比View高,而且你可以控制幀數。

4. SurfaceView在繪圖時使用l了雙緩衝機制,而View沒有。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章