Summary of Common Graphic Drawing Methods in Android

  • 2021-12-13 09:40:34
  • OfStack

Overview of Drawing Catalog Graphics View + Canvas SurfaceView + Canvas TextureView + Canvas SurfaceView + OpenGL ES GLSurfaceView + OpenGL ES TextureView + OpenGL ES Summarize

Overview of Graphic Drawing

Android platform provides rich official controls for developers to realize UI development, but in actual business, they often encounter various customization requirements, which must be realized by developers through self-drawing controls. Generally, Android is implemented in two ways: Canvas and OpenGL and ES, among which Canvas is implemented by means of Skia 2D vector graphics processing function library at the bottom of Android. How to draw graphics through Canvas and OpenGL? This must be implemented by relying on the View class provided by Android. The following is a combination of several common application methods, as follows:

Canvas

View + Canvas SurfaceView + Canvas TextureView + Canvas

OpenGL ES

SurfaceView + OpenGL ES GLSurfaceView + OpenGL ES TextureView + OpenGL ES

View + Canvas

This is a commonly used way to draw controls by overriding the onDraw (Canvas canvas) method of the View class. When you need to refresh the drawing, call the invalidate () method to have the View object refresh itself. This scheme is simple and involves less custom logic. The disadvantage is that the rendering logic is carried out in UI thread, the refresh efficiency is not high, and 3D rendering is not supported.


public class CustomView extends View {
    public CustomView(Context context) {
        super(context);
    }
 
    public CustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
 
    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        // draw whatever.
    }
}

SurfaceView + Canvas

Compared with View + Canvas, this method uses SurfaceView, so it will create its own Surface on WMS system of Android for rendering, and its rendering logic can be carried out in an independent thread, so its performance is more efficient than View + Canvas. However, it is usually necessary to create a drawing thread and implement SurfaceHolder. Callback interface to manage the life cycle of SurfaceView, which is slightly more complicated than View + Canvas. In addition, it still does not support 3D rendering, and Surface is not in View hierachy, and its display is not controlled by the attributes of View, so it cannot be translated, scaled, etc., nor can it be placed in other ViewGroup, and SurfaceView cannot be nested.


public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
 
    private boolean mRunning = false;
    private SurfaceHolder mSurfaceHolder;
 
    public CustomSurfaceView(Context context) {
        super(context);
        initView();
    }
 
    public CustomSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }
 
    public CustomSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }
 
    private void initView() {
        getHolder().addCallback(this);
    }
 
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mSurfaceHolder = holder;
        new Thread(this).start();
    }
 
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        mSurfaceHolder = holder;
    }
 
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mRunning = false;
    }
 
    @Override
    public void run() {
        mRunning = true;
        while (mRunning) {
            SystemClock.sleep(333);
            Canvas canvas = mSurfaceHolder.lockCanvas();
            if (canvas != null) {
                try {
                    synchronized (mSurfaceHolder) {
                        onRender(canvas);
                    }
                } finally {
                    mSurfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
        }
    }
 
    private void onRender(Canvas canvas) {
        // draw whatever.
    }
}

TextureView + Canvas

This method is somewhat similar to SurfaceView + Canvas, but because it is realized through TextureView, the defect that Surface is not in View hierachy can be abandoned. TextureView will not create a window separately in WMS, but will be used as an ordinary View in View hierachy, so it can be moved, rotated, scaled and animated like other ordinary View1. This method also has its own disadvantages. It must be used in hardware-accelerated windows, takes up more memory than SurfaceView, and is rendered in the main UI thread before 5.0, and has a separate rendering thread after 5.0.


public class CustomTextureView extends TextureView implements TextureView.SurfaceTextureListener, Runnable {
 
    private boolean mRunning = false;
    private SurfaceTexture mSurfaceTexture;
    private Surface mSurface;
    private Rect mRect;
 
    public CustomTextureView(Context context) {
        super(context);
        initView();
    }
 
    public CustomTextureView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }
 
    public CustomTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }
 
    private void initView() {
        setSurfaceTextureListener(this);
    }
 
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        mSurfaceTexture = surface;
        mRect = new Rect(0, 0, width, height);
        mSurface = new Surface(mSurfaceTexture);
        new Thread(this).start();
    }
 
    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        mSurfaceTexture = surface;
        mRect = new Rect(0, 0, width, height);
        mSurface = new Surface(mSurfaceTexture);
    }
 
    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        mRunning = false;
        return false;
    }
 
    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
 
    }
 
    @Override
    public void run() {
        mRunning = true;
        while (mRunning) {
            SystemClock.sleep(333);
            Canvas canvas = mSurface.lockCanvas(mRect);
            if (canvas != null) {
                try {
                    synchronized (mSurface) {
                        onRender(canvas);
                    }
                } finally {
                    mSurface.unlockCanvasAndPost(canvas);
                }
            }
        }
    }
 
    private void onRender(Canvas canvas) {
        canvas.drawColor(Color.RED);
        // draw whatever.
    }
}

All the above are common ways of 2D graphics rendering. If you want to perform 3D graphics rendering or advanced image processing (such as filters, AR, etc.), you must introduce OpenGL ES to achieve it. OpenGL ES (OpenGL for Embedded Systems) is a subset of OpenGL 3D graphics API, which is designed for embedded devices such as mobile phones, PDA and game consoles. It is a design standard for graphics rendering API, and different software and hardware developers may have different implementation methods in OpenGL API.

The following introduces how to render OpenGL ES on Android platform. There are usually three ways:

SurfaceView + OpenGL ES

EGL is the interface between OpenGL API and native window system. The platform independence of OpenGL ES is realized by EGL, and EGL shields the differences of different platforms. If you use OpenGL API to draw graphics, you must first build an EGL environment.

One common step for EGL rendering is:

-Obtain the EGLDisplay object, establish the connection with the local window system and call the eglGetDisplay method to obtain EGLDisplay.

Initializes the EGL method. After opening the connection, call the eglInitialize method to initialize.

-Obtain the EGLConfig object, determine the configuration information of the rendering surface, and call the eglChooseConfig method to obtain the EGLConfig.

Create rendering surface EGLSurface EGLSurface is created by EGLDisplay and EGLConfig, calling eglCreateWindowSurface or eglCreatePbufferSurface methods.

Create rendering context EGLContext through EGLDisplay and EGLConfig, call eglCreateContext method to create rendering context, and get EGLContext.

-Binding context binds EGLSurface, EGLContext and EGLDisplay through eglMakeCurrent method. After successful binding, OpenGLES environment is created, and then it can be rendered.

-Swap Buffer OpenGLES After drawing, use the eglSwapBuffers method to swap the front and back buffers to display the drawing content on the screen, while off-screen rendering does not need to call this method.

-When EGL is no longer needed after the EGL environment is drawn, it is necessary to cancel the binding of eglMakeCurrent and destroy EGLDisplay, EGLSurface and EGLContext objects.

The construction of the above EGL environment is complicated, so we will not explain too much here. The following can refer to its specific implementation through code:


public class OpenGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    private boolean mRunning = false;
    private SurfaceHolder mSurfaceHolder;
 
    public OpenGLSurfaceView(Context context) {
        super(context);
        initView();
    }
 
    public OpenGLSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }
 
    public OpenGLSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }
 
    private void initView() {
        getHolder().addCallback(this);
    }
 
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mSurfaceHolder = holder;
        new Thread(this).start();
    }
 
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        mSurfaceHolder = holder;
    }
 
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mRunning = false;
    }
 
    @Override
    public void run() {
        // Create 1 A EGL Instances 
        EGL10 egl = (EGL10) EGLContext.getEGL();
        //
        EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
        // Initialization EGLDisplay
        int[] version = new int[2];
        egl.eglInitialize(dpy, version);
 
        int[] configSpec = {
                EGL10.EGL_RED_SIZE,      5,
                EGL10.EGL_GREEN_SIZE,    6,
                EGL10.EGL_BLUE_SIZE,     5,
                EGL10.EGL_DEPTH_SIZE,   16,
                EGL10.EGL_NONE
        };
 
        EGLConfig[] configs = new EGLConfig[1];
        int[] num_config = new int[1];
        // Select config Create opengl Running environment 
        egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config);
        EGLConfig config = configs[0];
 
        EGLContext context = egl.eglCreateContext(dpy, config,
                EGL10.EGL_NO_CONTEXT, null);
        // Create a new surface
        EGLSurface surface = egl.eglCreateWindowSurface(dpy, config, mSurfaceHolder, null);
        // Will opengles Environment is set to the current 
        egl.eglMakeCurrent(dpy, surface, surface, context);
        // Gets the current opengles Canvas 
        GL10 gl = (GL10)context.getGL();
 
        mRunning = true;
        while (mRunning) {
            SystemClock.sleep(333);
            synchronized (mSurfaceHolder) {
                onRender(gl);
 
                // Display the drawing results to the screen 
                egl.eglSwapBuffers(dpy, surface);
            }
        }
 
        egl.eglMakeCurrent(dpy, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
        egl.eglDestroySurface(dpy, surface);
        egl.eglDestroyContext(dpy, context);
        egl.eglTerminate(dpy);
    }
 
    private void onRender(GL10 gl) {
        gl.glClearColor(1.0F, 0.0F, 0.0F, 1.0F);
        // Clears the screen and depth buffer.
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT
                | GL10.GL_DEPTH_BUFFER_BIT);
    }
}

As can be seen from the above code, compared with the drawing mode of SurfaceView + Canvas, there are mainly the following two changes:

The code of EGL environment construction is added before and after while (true) loop The parameters in the onRender () method use GL10 instead of Canvas

GLSurfaceView + OpenGL ES

Because of the tedious construction of EGL environment and the need to maintain one thread robustly, it is inconvenient to draw OpenGL directly using SurfaceView. Fortunately, Android platform provides GLSurfaceView class, which encapsulates these logics well, so that developers can quickly render OpenGL. To render graphics using the GLSurfaceView class, you need to implement the GLSurfaceView. Renderer interface, which provides an onDrawFrame (GL10 gl) method, in which you can implement specific rendering logic.


public class OpenGLGLSurfaceView extends GLSurfaceView implements GLSurfaceView.Renderer {
    public OpenGLGLSurfaceView(Context context) {
        super(context);
        setRenderer(this);
    }
 
    public OpenGLGLSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setRenderer(this);
    }
 
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // pass through
    }
 
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        gl.glViewport(0, 0, width, height);
    }
 
    @Override
    public void onDrawFrame(GL10 gl) {
        gl.glClearColor(1.0F, 0.0F, 0.0F, 1.0F);
        // Clears the screen and depth buffer.
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT
                | GL10.GL_DEPTH_BUFFER_BIT);
    }
}

TextureView + OpenGL ES

This method is similar to the use of SurfaceView + OpenGL ES. One advantage of using this method is that it is realized through TextureView, so it can abandon the defect that Surface is not in View hierachy. TextureView will not create a window separately in WMS, but as an ordinary View in View hierachy, so it can be moved, rotated, scaled and animated like other ordinary View1. When building an EGL environment using the TextureView class, note that the parameter passed in to eglCreateWindowSurface () is an SurfaceTexture instance.


public class OpenGLTextureView extends TextureView implements TextureView.SurfaceTextureListener, Runnable {
    private boolean mRunning = false;
    private SurfaceTexture mSurfaceTexture;
 
    public OpenGLTextureView(Context context) {
        super(context);
        initView();
    }
 
    public OpenGLTextureView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }
 
    public OpenGLTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }
 
    private void initView() {
        setSurfaceTextureListener(this);
    }
 
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        mSurfaceTexture = surface;
        new Thread(this).start();
    }
 
    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        mSurfaceTexture = surface;
    }
 
    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        mRunning = false;
        return false;
    }
 
    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
 
    }
 
    @Override
    public void run() {
        // Create 1 A EGL Instances 
        EGL10 egl = (EGL10) EGLContext.getEGL();
        //
        EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
        // Initialization EGLDisplay
        int[] version = new int[2];
        egl.eglInitialize(dpy, version);
 
        int[] configSpec = {
                EGL10.EGL_RED_SIZE,      5,
                EGL10.EGL_GREEN_SIZE,    6,
                EGL10.EGL_BLUE_SIZE,     5,
                EGL10.EGL_DEPTH_SIZE,   16,
                EGL10.EGL_NONE
        };
 
        EGLConfig[] configs = new EGLConfig[1];
        int[] num_config = new int[1];
        // Select config Create opengl Running environment 
        egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config);
        EGLConfig config = configs[0];
 
        EGLContext context = egl.eglCreateContext(dpy, config,
                EGL10.EGL_NO_CONTEXT, null);
        // Create a new surface
        EGLSurface surface = egl.eglCreateWindowSurface(dpy, config, mSurfaceTexture, null);
        // Will opengles Environment is set to the current 
        egl.eglMakeCurrent(dpy, surface, surface, context);
        // Gets the current opengles Canvas 
        GL10 gl = (GL10)context.getGL();
 
        mRunning = true;
        while (mRunning) {
            SystemClock.sleep(333);
            synchronized (mSurfaceTexture) {
                onRender(gl);
 
                // Display the drawing results to the screen 
                egl.eglSwapBuffers(dpy, surface);
            }
        }
 
        egl.eglMakeCurrent(dpy, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
        egl.eglDestroySurface(dpy, surface);
        egl.eglDestroyContext(dpy, context);
        egl.eglTerminate(dpy);
    }
 
    private void onRender(GL10 gl) {
        gl.glClearColor(1.0F, 0.0F, 1.0F, 1.0F);
        // Clears the screen and depth buffer.
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT
                | GL10.GL_DEPTH_BUFFER_BIT);
    }
}

Code sample reference

github. com/sunjinbo/hi …

Summarize


Related articles: