SurfaceView and View

The SurfaceView can redraw the screen in a separate thread, while the View must update the screen in the main thread of the UI. Updating the screen in the main thread of the UI can cause problems, for example if you take too long to update the screen, your main UI thread will be blocked by the function you are drawing. You won’t be able to respond to buttons, touch screens, etc. When using the SurfaceView it does not block your MAIN UI thread because it updates the screen in a new thread. But this brings up another problem, which is event synchronization. For example, if you touch the screen and you need thread processing in the SurfaceView, you usually need to have an event Queue designed to hold touch events, which is a little bit more complicated because it involves thread synchronization.

So based on the above, according to the characteristics of rendering, generally divided into two categories.

1 Passive update screen. So for chess games, you can use View. Since screen updates rely on onTouch, you can use invalidate directly. Because in this case, this Touch and the next Touch take a longer time, there is no impact.

2 Actively update. Let’s say a person is running. This requires a single thread to constantly redraw the human state to avoid blocking the main UI thread. So obviously the View doesn’t work and you need the SurfaceView to control it.

SurfaceView profile

In general, application views are drawn in the same GUI thread. This main application thread is also used to handle all user interactions (for example, button clicks or text input). We can move blocking prone processing to background threads. Unfortunately, this cannot be done for a View’s onDraw method, as modifying a GUI element from a background thread is explicitly forbidden. When you need to update the View’s UI quickly, or when rendering code blocks GUI threads for too long, SurfaceView is the perfect solution.

SurfaceView encapsulates a Surface object, not a Canvas. This is important because the Surface can be drawn using background threads. This is especially useful for resource-sensitive operations, or where quick updates or high frame rates are required, such as using 3D graphics, creating games, or previewing cameras in real time.

Drawing independently of GUI threads comes at the cost of additional memory consumption, so while it is an efficient way to create custom views — and sometimes even necessary — you should still be cautious when using surfaceViews.

  1. When should YOU use the SurfaceView?

The SurfaceView works in exactly the same way as any View derived class. You can apply animations just like any other View and put them in the layout.

  1. Create a new SurfaceView control

To create a new SurfaceView, create a new class that extends the SurfaceView and implements the Surfaceholder.callback.

The SurfaceHolder callback notifies the View when the underlying Surface is created and destroyed and passes it a reference to the SurfaceHolder object, which contains the currently valid Surface.

A typical Surface View design model includes a class derived from Thread that receives a reference to the current SurfaceHolder and updates it independently.

Use the SurfaceView to draw the bitmap

The implementation principle is as follows:

1. First get the View object of the control. We can new a control object and implement its onMeasure, onLayout, onDraw methods. The original View object might have a touch event response, but this is a preview, so don’t worry about it. All you need to do is set the data of the original View object.

2, Need to get the View’s bitmap. A separate thread can be opened to convert the View to the bitmap and draw the resulting bitmap to the SurfaceView canvas.

public class RenderThread extends Thread{
    private final Object LOCK=new Object();
    private volatile boolean isRunning=false;
    private View preview;
    public RenderThread(@NonNull View preview) {
        super("RenderThread");
        this.preview = preview;
    }

    @Override
    public synchronized void start() {
        isRunning=true;
        super.start();
    }

    @Override
    public void run() {
        super.run();
        while(isRunning){ //Drawing the preview on this canvas. synchronized (LOCK) { preview.measure(View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED)); int measuredWidth = preview.getMeasuredWidth(); int measuredHeight = preview.getMeasuredHeight(); Preview the layout (0, 0, measuredWidth, measuredHeight); final int width = getWidth();float scale=width*1f/measuredWidth;
                final int height = (int) (scale*measuredHeight);
                setMeasuredDimension(width,height);
                post(new Runnable() {
                    @Override
                    public void run() { SurfaceHolder holder = getHolder(); holder.setFixedSize(width,height); }}); previewBitmap=Bitmap.createBitmap(width,height, Bitmap.Config.RGB_565); Canvas canvas = new Canvas(previewBitmap); canvas.save(); canvas.scale(scale,scale); //Draw the preview. preview.draw(canvas); canvas.restore(); } SurfaceHolder holder = getHolder(); Canvas canvas = null; try { canvas = holder.lockCanvas();if(null! =canvas&&null! =previewBitmap){ canvas.drawBitmap(previewBitmap, 0, 0, null); } } finally {if(null! =canvas){ holder.unlockCanvasAndPost(canvas); } } synchronized (LOCK) { try { LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * notify the thread to update the preview again. */ voidupdatePreView(){
        synchronized (LOCK){
            LOCK.notify();
        }
    }

    public void quit() {
        synchronized (LOCK){
            isRunning=false; LOCK.notify(); }}}Copy the code

3. Monitor the state change of the control. The main control is the slide monitor and control state (click effect) changes.

Status monitoring:



@Override
public void childDrawableStateChanged(final View child) {
    super.childDrawableStateChanged(child);
    if(null! =layoutChildDrawableStateChangeListener&&hasWindowFocus()){ int currentViewStateFlag = 0;if(child.isSelected()) currentViewStateFlag|=LayoutParams.VIEW_STATE_SELECTED;
        if(child.isEnabled()) currentViewStateFlag|=LayoutParams.VIEW_STATE_ENABLED;
        if(child.isActivated()) currentViewStateFlag|=LayoutParams.VIEW_STATE_ACTIVATED;
        if(child.isFocused()) currentViewStateFlag|=LayoutParams.VIEW_STATE_FOCUSED;
        if(child.isHovered()) currentViewStateFlag|=LayoutParams.VIEW_STATE_HOVERED;
        if(child.isPressed()) currentViewStateFlag|=LayoutParams.VIEW_STATE_PRESSED;
        final LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
        //To avoid dispatch to many times, We use our custom view state flag to check if the view state is changed.
        if(currentViewStateFlag! =layoutParams.viewStateFlag){ layoutParams.viewStateFlag=currentViewStateFlag; Update the corresponding Child Viewif(null! =previewBitmap){ SurfaceHolder holder = getHolder();if(null! =holder){ //Update the bitmap Canvas canvas = new Canvas(previewBitmap); canvas.save(); int measuredWidth = preview.getMeasuredWidth(); final int width = getWidth();float scale=width*1f/measuredWidth;
                    canvas.scale(scale,scale);
                    previewable.onChildChange(canvas,child);
                    canvas.restore();
    
                    Canvas lockCanvas=null;
                    try {
                        //Draw the cached bitmap.
                        lockCanvas = holder.lockCanvas();
                        if(null! = lockCanvas) {lockCanvas. DrawBitmap (previewBitmap, 0, 0, null); drawScrollRect(lockCanvas,zoomLayout,preview,scaleX, scaleY); } } finally {if(null! =lockCanvas) { holder.unlockCanvasAndPost(lockCanvas); } } } } } } }Copy the code