天天看點

講講Android為自定義view提供的SurfaceView系列文章目錄前言一、Android為什麼會提供SurfaceView二、先看看Android Demo的實作三、繼承SurfaceView實作四、放一個使用案例源碼五、拓展一下(以下内容來源于網絡)

系列文章目錄

講講Android為自定義view提供的SurfaceView

文章目錄

  • 系列文章目錄
  • 前言
  • 一、Android為什麼會提供SurfaceView
  • 二、先看看Android Demo的實作
    • 1.實作接口以及接口定義的方法
    • 2.與Activity生命周期進行綁定
    • 3.完成初始化操作
    • 4.實作
    • 5.運作
  • 三、繼承SurfaceView實作
    • 1.自定義類繼承自SurfaceView,并且實作兩個接口以及接口定義的方法。
    • 2.初始化
    • 3.步驟與Android Demo的實作-4.實作類似
  • 四、放一個使用案例源碼
  • 五、拓展一下(以下内容來源于網絡)

前言

前幾天發表了幾篇在自定義view中通過修改值實作動态效果的文章。起到主要作用的是調用重新整理界面的方法。但是假設繪制的過程邏輯比較複雜,并且界面更新頻繁,這時候就會造成界面的卡頓。十分影響使用者體驗感。

靈感來源于,Android官方demo(效果圖如下)

講講Android為自定義view提供的SurfaceView系列文章目錄前言一、Android為什麼會提供SurfaceView二、先看看Android Demo的實作三、繼承SurfaceView實作四、放一個使用案例源碼五、拓展一下(以下内容來源于網絡)

一、Android為什麼會提供SurfaceView

View是通過重新整理來重繪視圖,并且有一個重新整理的間隔,當繪制過程邏輯很複雜加上界面更新還非常頻繁時,就可能無法在間隔内完成繪制,就會造成界面效果的卡頓,影響使用者體驗,為此Android提供了SurfaceView來解決這一問題。

二、先看看Android Demo的實作

1.實作接口以及接口定義的方法

public void surfaceCreated(SurfaceHolder holder) {
    synchronized (mDrawingThread) {
        mDrawingThread.mSurface = holder;
        mDrawingThread.notify();
    }
}

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    //這裡不需要做任何事情;繪制線程将從畫布中擷取
}

public void surfaceRedrawNeeded(SurfaceHolder holder) {
}

public void surfaceDestroyed(SurfaceHolder holder) {
    //我們需要告訴繪圖線程停止
    synchronized (mDrawingThread) {
        mDrawingThread.mSurface = holder;
        mDrawingThread.notify();
        while (mDrawingThread.mActive) {
            try {
                mDrawingThread.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
           

2.與Activity生命周期進行綁定

@Override
protected void onPause() {
    super.onPause();

    //當我們暫停時,確定繪制線程沒有運作。
    synchronized (mDrawingThread) {
        mDrawingThread.mRunning = false;
        mDrawingThread.notify();
    }
}

@Override
protected void onResume() {
    super.onResume();

    //讓繪圖線程繼續運作。
    synchronized (mDrawingThread) {
        mDrawingThread.mRunning = true;
        mDrawingThread.notify();
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();

    //確定繪圖線程消失。
    synchronized (mDrawingThread) {
        mDrawingThread.mQuit = true;
        mDrawingThread.notify();
    }
}
           

3.完成初始化操作

4.實作

  • 通過

    lockCanvas()

    方法獲得Canvas對象
//鎖定畫布進行繪圖。
Canvas canvas = mSurface.lockCanvas();
if (canvas == null) {
    Log.i("WindowSurface", "Failure locking canvas");
    continue;
}
           
  • 在子線程中使用Canvas對象進行繪制
// 更新圖形
if (!mInitialized) {
    mInitialized = true;
    mPoint1.init(canvas.getWidth(), canvas.getHeight(), mMinStep);
    mPoint2.init(canvas.getWidth(), canvas.getHeight(), mMinStep);
    mColor.init(127, 127, 1);
} else {
    mPoint1.step(canvas.getWidth(), canvas.getHeight(),
            mMinStep, mMaxStep);
    mPoint2.step(canvas.getWidth(), canvas.getHeight(),
            mMinStep, mMaxStep);
    mColor.step(127, 127, 1, 3);
}
//顔色的效果
mBrightLine+=2;
if (mBrightLine > (NUM_OLD*2)) {
    mBrightLine = -2;
}

// 清理背景
canvas.drawColor(mBackground.getColor());

// 畫舊線
for (int i=mNumOld-1; i>=0; i--) {
    mForeground.setColor(mOldColor[i] | makeGreen(i));
    mForeground.setAlpha(((NUM_OLD-i) * 255) / NUM_OLD);
    int p = i*4;
    canvas.drawLine(mOld[p], mOld[p+1], mOld[p+2], mOld[p+3], mForeground);


}

// 畫新線
int red = (int)mColor.x + 128;
if (red > 255) red = 255;
int blue = (int)mColor.y + 128;
if (blue > 255) blue = 255;
int color = 0xff000000 | (red<<16) | blue;
mForeground.setColor(color | makeGreen(-2));
canvas.drawLine(mPoint1.x, mPoint1.y, mPoint2.x, mPoint2.y, mForeground);




// 添加新的線條
if (mNumOld > 1) {
    System.arraycopy(mOld, 0, mOld, 4, (mNumOld-1)*4);
    System.arraycopy(mOldColor, 0, mOldColor, 1, mNumOld-1);
}
if (mNumOld < NUM_OLD) mNumOld++;
mOld[0] = mPoint1.x;
mOld[1] = mPoint1.y;
mOld[2] = mPoint2.x;
mOld[3] = mPoint2.y;
mOldColor[0] = color;
           
  • 使用

    unlockCanvasAndPost()

    方法将畫布内容進行送出
//全部完成
mSurface.unlockCanvasAndPost(canvas);
           

5.運作

//告訴活動的視窗,我們想做我們自己的繪制
getWindow().takeSurface(this);
//這是将繪制到我們的表面的線程。
mDrawingThread = new DrawingThread();
mDrawingThread.start();
           

三、繼承SurfaceView實作

1.自定義類繼承自SurfaceView,并且實作兩個接口以及接口定義的方法。

public class MyView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    //建立
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
       
    }
    //改變
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }
    //銷毀
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }
    //子線程
    @Override
    public void run() {
    //子線程中執行的繪圖邏輯
    }
}
           

2.初始化

mSurfaceHolder = getHolder();
        mSurfaceHolder.addCallback(this);
       ......
           

3.步驟與Android Demo的實作-4.實作類似

  • 通過

    lockCanvas()

    方法獲得Canvas對象
  • 在子線程中使用Canvas對象進行繪制(run())
  • 使用

    unlockCanvasAndPost()

    方法将畫布内容進行送出
try {
            mCanvas = mSurfaceHolder.lockCanvas();
            mCanvas.drawColor(Color.WHITE);
            //繪制的邏輯
            .....
        }catch (Exception e){

        }finally {
            if (mCanvas != null){
                //釋放canvas對象并送出畫布
                mSurfaceHolder.unlockCanvasAndPost(mCanvas);
            }
        }
           

四、放一個使用案例源碼

效果見:Android自定義view之線條等待動畫(靈感來源:金鏟鏟之戰)

public class MyView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    private SurfaceHolder mSurfaceHolder;
    private Canvas mCanvas;
    private int mWidth;
    private int mHeight;
    private int useWidth, minwidth;
    private boolean viewContinue=true,viewContinue1=true;
    private float mSweep,mSweep1;
    private boolean runDrawing;
    private Paint mPaint;
    public MyView(Context context) {
        super(context);
        initView();
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    //建立
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        runDrawing = true;
        new Thread(this).start();
    }
    //改變
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }
    //銷毀
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        runDrawing=false;
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        useWidth = mWidth;
        if (mWidth > mHeight) {
            useWidth = mHeight;
        }

    }
    //子線程
    @Override
    public void run() {
        while (runDrawing){
            draw();
        }
    }

    //繪制
    private void draw() {
        try {
            //獲得canvas對象
            mCanvas = mSurfaceHolder.lockCanvas();
            //繪制背景顔色
            mCanvas.drawColor(Color.WHITE);
            
            minwidth = useWidth / 10;

            mCanvas.drawLine(minwidth*5,minwidth*5,minwidth*5+mSweep,minwidth*5+mSweep,mPaint);
            mCanvas.drawLine(minwidth*5,minwidth*5,minwidth*5-mSweep,minwidth*5-mSweep,mPaint);
            mCanvas.drawLine(minwidth*5,minwidth*5,minwidth*5+mSweep,minwidth*5-mSweep,mPaint);
            mCanvas.drawLine(minwidth*5,minwidth*5,minwidth*5-mSweep,minwidth*5+mSweep,mPaint);

            mCanvas.drawLine(minwidth*5+mSweep,minwidth*5+mSweep,minwidth*5+mSweep,minwidth*5+mSweep-mSweep1,mPaint);
            mCanvas.drawLine(minwidth*5-mSweep,minwidth*5-mSweep,minwidth*5-mSweep,minwidth*5-mSweep+mSweep1,mPaint);
            mCanvas.drawLine(minwidth*5+mSweep,minwidth*5-mSweep,minwidth*5+mSweep-mSweep1,minwidth*5-mSweep,mPaint);
            mCanvas.drawLine(minwidth*5-mSweep,minwidth*5+mSweep,minwidth*5-mSweep+mSweep1,minwidth*5+mSweep,mPaint);


            if (viewContinue&&viewContinue1){
                mSweep += 2;
                if (mSweep > minwidth*2) {
                    viewContinue=false;

                }

            }
            if (!viewContinue&&viewContinue1){
                mSweep1 += 4;
                if (mSweep1 > 4*minwidth) {
                    viewContinue1=false;
                    viewContinue=true;

                }
            }
            if (viewContinue&&!viewContinue1){
                if (mSweep1 <=0) {
                    mSweep-=4;
                    if (mSweep<0){
                        viewContinue=true;
                        viewContinue1=true;
                    }

                }else{
                    mSweep1 -= 2;
                }
            }
            //重新整理View
            invalidate();
        }catch (Exception e){

        }finally {
            if (mCanvas != null){
                //釋放canvas對象并送出畫布
                mSurfaceHolder.unlockCanvasAndPost(mCanvas);
            }
        }
    }
    private void initView(){
        mSurfaceHolder = getHolder();
        //注冊回調方法
        mSurfaceHolder.addCallback(this);
        setFocusable(true);
        setKeepScreenOn(true);
        setFocusableInTouchMode(true);
        //初始化畫筆
        initPaint();
    }

    private void initPaint() {
        mPaint = new Paint();        //建立畫筆對象
        mPaint.setColor(Color.BLACK);    //設定畫筆顔色
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(4f);     //設定畫筆寬度為10px
        mPaint.setAntiAlias(true);     //設定抗鋸齒
        mPaint.setAlpha(255);        //設定畫筆透明度
    }
}
           

五、拓展一下(以下内容來源于網絡)

View和SurfaceView的差別:

  • View适用于主動更新的情況,而SurfaceView則适用于被動更新的情況,比如頻繁重新整理界面。
  • View在主線程中對頁面進行重新整理,而SurfaceView則開啟一個子線程來對頁面進行重新整理。
  • View在繪圖時沒有實作雙緩沖機制,SurfaceView在底層機制中就實作了雙緩沖機制。