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
- through
lockCanvas()
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
- use
unlockCanvasAndPost()
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
- through
lockCanvas()
Method to get a Canvas object - Drawing with Canvas object in child thread (run())
- use
unlockCanvasAndPost()
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.