1. 程式人生 > >Android繪圖機制與處理技巧(五)——View的孿生兄弟SurfaceView

Android繪圖機制與處理技巧(五)——View的孿生兄弟SurfaceView

SurfaceView與View的區別

View通過重新整理來重繪檢視,Android系統通過發出VSYNC訊號來進行螢幕的重繪,重新整理的間隔時間為16ms。如果在16ms內View完成了所需要執行的所有操作,那麼在使用者的視覺上,就不會產生卡頓的感覺;而如果執行的操作邏輯太多,特別是需要頻繁重新整理的介面上,例如遊戲介面,那麼就會不斷阻塞主執行緒,從而導致畫面卡頓。很多時候,在自定義View的Log中經常會看到如下所示的警告。

“Skipped 47 frames! The application may be doing too much work on its main thread”

這些警告的產生,很多情況下就是因為在繪製過程中,處理邏輯太多造成的。

為了避免這一問題的產生,Android系統提供了SurfaceView元件來解決這個問題。SurfaceView可以說是View的孿生兄弟,但它與View還是有所不同,它們的區別主要體現在以下幾點。

  • View主要適用於主動更新的情況下,而SurfaceView主要適用於被動更新,例如頻繁地重新整理。
  • View在主執行緒中對畫面進行重新整理,而SurfaceView通常會通過一個子執行緒來進行頁面的重新整理。
  • View在繪圖時沒有使用雙緩衝機制,而SurfaceView在底層實現機制中就已經實現了雙緩衝機制。

總之,如果你的自定義View需要頻繁重新整理,或者重新整理時資料處理量比較大,那麼你就可以考慮使用SurfaceView來取代View了。

SurfaceView的使用

使用SurfaceView的模板程式碼:

/**
 * Created by Administrator on 2016/6/1.
 * 使用SurfaceView的模板程式碼
 */
public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    // SurfaceHolder
    private SurfaceHolder mHolder;
    // 用於繪圖的Canvas
    private Canvas mCanvas;
    // 子執行緒標誌位
private boolean mIsDrawing; public SurfaceViewTemplate(Context context) { super(context); initView(); } public SurfaceViewTemplate(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } private void initView() { //初始化一個SurfaceHolder物件 mHolder = getHolder(); //註冊SurfaceHolder的回撥方法 mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); setKeepScreenOn(true); //mHolder.setFormat(PixelFormat.OPAQUE); } //SurfaceView的建立 @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { mIsDrawing = true; //開啟子執行緒 new Thread(this).start(); } //SurfaceView的改變 @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { } //SurfaceView的銷燬 @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { mIsDrawing = false; } @Override public void run() { //通過迴圈來不停地進行繪製 while(mIsDrawing){ draw(); } } private void draw() { try { /** * 獲取當前的Canvas繪圖物件。 * 獲取到的Canvas物件還是繼續上次的Canvas物件,而不是一個新的物件。因此,之前的繪圖操作都將保留, * 如果需要擦除,則可以在繪製前,通過drawColor()方法來進行清屏操作。 */ mCanvas = mHolder.lockCanvas(); //draw something } catch (Exception e){ } finally { if(mCanvas != null) //將該方法放到finally程式碼塊中,來保證每次都能將畫布內容進行提交 mHolder.unlockCanvasAndPost(mCanvas); } } }

SurfaceView例項

正弦曲線

在介面上不斷繪製一個正弦曲線,類似示波器、心電圖、股票走勢圖等。只需要不斷修改橫縱座標的值,並讓它們滿足正弦函式即可。因此,使用一個Path物件來儲存正弦函式上的座標點,在子執行緒的while迴圈中,不斷改變橫縱座標值,程式碼如下:

    @Override
    public void run() {
        while(mIsDrawing){
            draw();
            x += 1;
            y = (int) (100 * Math.sin(x * 2 * Math.PI / 180) + 400);
            mPath.lineTo(x, y);
        }
    }

    private void draw() {
        try {
            mCanvas = mHolder.lockCanvas();
            //SurfaceView的背景
            mCanvas.drawColor(Color.WHITE);
            mCanvas.drawPath(mPath, mPaint);
        } catch (Exception e){

        } finally {
            if(mCanvas != null)
                mHolder.unlockCanvasAndPost(mCanvas);
        }
    }

繪製效果如下圖:

這裡寫圖片描述

繪圖板

使用SurfaceView來實現一個簡單的繪圖板,繪圖的方法與在View中進行繪圖所使用的方法一樣,也是通過Path物件來記錄手指滑動的路徑來進行繪圖。程式碼如下:

    @Override
    public void run() {
        /**
         * 在前面的模板程式碼中,線上程中不斷地呼叫draw()方法來進行繪製,擔有的時候繪製也不用這麼頻繁。
         * 因此可以在子執行緒中進行sleep操作,儘可能地節省系統資源。
         */
        long start = System.currentTimeMillis();
        while(mIsDrawing){
            draw();
        }
        long end = System.currentTimeMillis();
        /**
         * 通過判斷draw()方法所使用的邏輯時長來確定sleep的時長,這是一個非常通用的解決方案,程式碼中的100ms是
         * 一個大致的經驗值,這個值的取值一般在50ms到100ms之間。
         */
        if(end - start < 100){
            try {
                Thread.sleep(100 - (end - start));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void draw() {
        try {
            mCanvas = mHolder.lockCanvas();
            mCanvas.drawColor(Color.WHITE);
            mCanvas.drawPath(mPath, mPaint);
        } catch (Exception e){

        } finally {
            if(mCanvas != null)
                mHolder.unlockCanvasAndPost(mCanvas);
        }
    }

    //記錄手指滑動的路徑
    @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;
    }

繪製效果如下圖:

這裡寫圖片描述

程式碼地址