This is the 30th day of my participation in the August Text Challenge.More challenges in August

Series of articles

Talk about Android SurfaceView for custom Views


preface

A few days ago, I published several articles about dynamic effects in custom views by modifying values. The main function is to call the method that refreshes the interface. However, if the drawing process logic is complex and the interface is updated frequently, the interface will be stuck. It affects the user experience.

Inspiration comes from Android official demo (renderings below)


Why does Android provide SurfaceView

View is to redraw the View by refreshing, and there is a refresh interval, when the drawing process logic is very complex and the interface update is very frequent, it may not be completed within the interval, resulting in the interface effect of lag, affecting the user experience, so Android provides SurfaceView to solve this problem.

First, look at the implementation of Android Demo

1. Implement the interface and the method of interface definition

. implements SurfaceHolder.Callback2Copy the code
public void surfaceCreated(SurfaceHolder holder) {
    synchronized(mDrawingThread) { mDrawingThread.mSurface = holder; mDrawingThread.notify(); }}public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    // There is no need to do anything; The drawing thread is fetched from the canvas
}

public void surfaceRedrawNeeded(SurfaceHolder holder) {}public void surfaceDestroyed(SurfaceHolder holder) {
    // We need to tell the drawing thread to stop
    synchronized (mDrawingThread) {
        mDrawingThread.mSurface = holder;
        mDrawingThread.notify();
        while (mDrawingThread.mActive) {
            try {
                mDrawingThread.wait();
            } catch(InterruptedException e) { e.printStackTrace(); }}}}Copy the code

2. Bind to the Activity life cycle

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

    // When we pause, make sure the drawing thread is not running.
    synchronized (mDrawingThread) {
        mDrawingThread.mRunning = false; mDrawingThread.notify(); }}@Override
protected void onResume(a) {
    super.onResume();

    // Let the drawing thread continue running.
    synchronized (mDrawingThread) {
        mDrawingThread.mRunning = true; mDrawingThread.notify(); }}@Override
protected void onDestroy(a) {
    super.onDestroy();

    // Make sure the drawing thread disappears.
    synchronized (mDrawingThread) {
        mDrawingThread.mQuit = true; mDrawingThread.notify(); }}Copy the code

3. Complete the initialization

4. To achieve

  • throughlockCanvas()Method to get a Canvas object
// Lock the canvas for drawing.
Canvas canvas = mSurface.lockCanvas();
if (canvas == null) {
    Log.i("WindowSurface"."Failure locking canvas");
    continue;
}
Copy the code
  • Draw using a Canvas object in the child thread
// Update the graph 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); } // Color effect mBrightLine+=2; if (mBrightLine > (NUM_OLD*2)) { mBrightLine = -2; } // Clean up the 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); } // draw a new line 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); // Add new line 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;Copy the code
  • useunlockCanvasAndPost()Method to submit the canvas content
// All done
mSurface.unlockCanvasAndPost(canvas);
Copy the code

5. Run

// Tell the active window that we want to do our own drawing
getWindow().takeSurface(this);
// This is the thread that will draw to our surface.
mDrawingThread = new DrawingThread();
mDrawingThread.start();
Copy the code

Third, inherit SurfaceView implementation

1. Custom classes inherit from SurfaceView and implement two interfaces and methods defined by the interfaces.

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); Public void surfaceCreated(SurfaceHolder) {} Public void surfaceCreated(SurfaceHolder) surfaceChanged(SurfaceHolder holder, int format, int width, Public void destroyed (surfaceDestroyed) {} public void destroyed (surfaceDestroyed) { {// Drawing logic executed in child thread}}Copy the code

2. The initialization

        mSurfaceHolder = getHolder();
        mSurfaceHolder.addCallback(this); .Copy the code

3. The steps are similar to the implementation of Android Demo -4

  • throughlockCanvas()Method to get a Canvas object
  • Drawing with Canvas object in child thread (run())
  • useunlockCanvasAndPost()Method to submit the canvas content
try { mCanvas = mSurfaceHolder.lockCanvas(); mCanvas.drawColor(Color.WHITE); // Draw the logic..... }catch (Exception e){ }finally { if (mCanvas ! = null) {/ / release the canvas object and submit the canvas mSurfaceHolder. UnlockCanvasAndPost (mCanvas); }}Copy the code

Four, put a use case source code

Android custom View line waiting animation (inspiration: Golden Shovel Battle)

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);
    }
    / / create
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        runDrawing = true;
        new Thread(this).start();
    }
    / / change
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}/ / destroy
    @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; }}/ / the child thread
    @Override
    public void run(a) {
        while(runDrawing){ draw(); }}/ / to draw
    private void draw(a) {
        try {
            // Get the canvas object
            mCanvas = mSurfaceHolder.lockCanvas();
            // Draw the background color
            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; }}/ / refresh the View
            invalidate();
        }catch (Exception e){

        }finally {
            if(mCanvas ! =null) {// Release the canvas and submit the canvasmSurfaceHolder.unlockCanvasAndPost(mCanvas); }}}private void initView(a){
        mSurfaceHolder = getHolder();
        // Register the callback method
        mSurfaceHolder.addCallback(this);
        setFocusable(true);
        setKeepScreenOn(true);
        setFocusableInTouchMode(true);
        // Initialize the brush
        initPaint();
    }

    private void initPaint(a) {
        mPaint = new Paint();        // Create a brush object
        mPaint.setColor(Color.BLACK);    // Set the brush color
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(4f);     // Set the brush width to 10px
        mPaint.setAntiAlias(true);     // Set anti-aliasing
        mPaint.setAlpha(255);        // Set the brush transparency}}Copy the code

Five, expand (the following content comes from the network)

The difference between a View and SurfaceView

  • The View works for active updates, while the SurfaceView works for passive updates, such as frequent interface refreshes.
  • The View refreshes the page in the main thread, while the SurfaceView opens a child thread to refresh the page.
  • The View does not implement double buffering when drawing; the SurfaceView implements double buffering in the underlying mechanism.