Summary of Common Graphic Drawing Methods in Android
- 2021-12-13 09:40:34
- OfStack
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 + CanvasOpenGL 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 …