Android常见图形绘制方式
图形绘制概述
Android平台提供丰富的官方控件给开发者实现界面UI开发,但在实际业务中经常会遇到各种各样的定制化需求,这必须由开发者通过自绘控件的方式来实现。通常Android提供了Canvas和OpenGL ES两种方式来实现,其中Canvas借助于Android底层的Skia 2D向量图形处理函数库来实现的。具体如何通过Canvas和OpenGL来绘制图形呢?这必须依赖于Android提供的View类来具体实现,下面组合几种常见的应用方式,如下所示:
Canvas
- View + Canvas
- SurfaceView + Canvas
- TextureView + Canvas
OpenGL ES
- SurfaceView + OpenGL ES
- GLSurfaceView + OpenGL ES
- TextureView + OpenGL ES
View + Canvas
这是一种通常使用的自绘控件方式,通过重写View类的onDraw(Canvas canvas)方法实现。当需要刷新绘制图形时,调用invalidate()方法让View对象自身进行刷新。该方案比较简单,涉及自定义逻辑较少,缺点是绘制逻辑在UI线程中进行,刷新效率不高,且不支持3D渲染。
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
这种方式相对于View + Canvas方式在于使用SurfaceView,因此会在Android的WMS系统上创建一块自己的Surface进行渲染绘制,其绘制逻辑可以在独立的线程中进行,因此性能相对于View + Canvas方式更高效。但通常情况下需要创建一个绘制线程,以及实现SurfaceHolder.Callback接口来管理SurfaceView的生命周期,其实现逻辑相比View + Canvas略复杂。另外它依然不支持3D渲染,且Surface因不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中,SurfaceView 不能嵌套使用。
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
该方式同SurfaceView + Canvas方式有些类似,但由于它是通过TextureView来实现的,所以可以摒弃Surface不在View hierachy中缺陷,TextureView不会在WMS中单独创建窗口,而是作为View hierachy中的一个普通View,因此可以和其它普通View一样进行移动,旋转,缩放,动画等变化。这种方式也有自身缺点,它必须在硬件加速的窗口中才能使用,占用内存比SurfaceView要高,在5.0以前在主UI线程渲染,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.
}
}
以上都是2D图形渲染常见的方式,如果想要进行3D图形渲染或者是高级图像处理(比如滤镜、AR等效果),就必须得引入OpenGL ES来实现了。OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三维图形 API 的子集,针对手机、PDA和游戏主机等嵌入式设备而设计,是一种图形渲染API的设计标准,不同的软硬件开发商在OpenGL API内部可能会有不同的实现方式。下面介绍一下在Android平台上,如何进行OpenGL ES渲染绘制,通常有以下三种方式:
SurfaceView + OpenGL ES
EGL是OpenGL API和原生窗口系统之间的接口,OpenGL ES 的平台无关性正是借助 EGL 实现的,EGL 屏蔽了不同平台的差异。如果使用OpenGL API来绘制图形就必须先构建EGL环境。
通常使用 EGL 渲染的一般步骤:
- 获取 EGLDisplay对象,建立与本地窗口系统的连接调用eglGetDisplay方法得到EGLDisplay。
- 初始化EGL方法,打开连接之后,调用eglInitialize方法初始化。
- 获取EGLConfig对象,确定渲染表面的配置信息调用eglChooseConfig方法得到 EGLConfig。
- 创建渲染表面EGLSurface通过EGLDisplay和EGLConfig,调用eglCreateWindowSurface或eglCreatePbufferSurface方法创建渲染表面得到EGLSurface。
- 创建渲染上下文EGLContext通过EGLDisplay和EGLConfig,调用eglCreateContext方法创建渲染上下文,得到EGLContext。
- 绑定上下文通过eglMakeCurrent 方法将 EGLSurface、EGLContext、EGLDisplay 三者绑定,绑定成功之后OpenGLES环境就创建好了,接下来便可以进行渲染。
- 交换缓冲OpenGLES 绘制结束后,使用eglSwapBuffers方法交换前后缓冲,将绘制内容显示到屏幕上,而屏幕外的渲染不需要调用此方法。
- 释放EGL环境绘制结束后,不再需要使用EGL时,需要取消eglMakeCurrent的绑定,销毁 EGLDisplay、EGLSurface、EGLContext三个对象。
以上EGL环境构建比较复杂,这里先不做过多解释,下面可以通过代码参考其具体实现:
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() {
//创建一个EGL实例
EGL10 egl = (EGL10) EGLContext.getEGL();
//
EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
//初始化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];
//选择config创建opengl运行环境
egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config);
EGLConfig config = configs[0];
EGLContext context = egl.eglCreateContext(dpy, config,
EGL10.EGL_NO_CONTEXT, null);
//创建新的surface
EGLSurface surface = egl.eglCreateWindowSurface(dpy, config, mSurfaceHolder, null);
//将opengles环境设置为当前
egl.eglMakeCurrent(dpy, surface, surface, context);
//获取当前opengles画布
GL10 gl = (GL10)context.getGL();
mRunning = true;
while (mRunning) {
SystemClock.sleep(333);
synchronized (mSurfaceHolder) {
onRender(gl);
//显示绘制结果到屏幕上
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);
}
}
从上面的代码可以看到,相对于SurfaceView + Canvas的绘制方式,主要有以下两点变化:
- 在while(true)循环前后增加了EGL环境构造的代码
- onRender()方法内参数用的是GL10而不是Canvas
GLSurfaceView + OpenGL ES
由于构建EGL环境比较繁琐,以及还需要健壮地维护一个线程,直接使用SurfaceView进行OpenGL绘制并不方便。幸好Android平台提供GLSurfaceView类,很好地封装了这些逻辑,使开发者能够快速地进行OpenGL的渲染开发。要使用GLSurfaceView类进行图形渲染,需要实现GLSurfaceView.Renderer接口,该接口提供一个onDrawFrame(GL10 gl)方法,在该方法内实现具体的渲染逻辑。
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
该方式跟SurfaceView + OpenGL ES使用方法比较类似,使用该方法有个好处是它是通过TextureView来实现的,所以可以摒弃Surface不在View hierachy中缺陷,TextureView不会在WMS中单独创建窗口,而是作为View hierachy中的一个普通View,因此可以和其它普通View一样进行移动,旋转,缩放,动画等变化。这里使用TextureView类在构建EGL环境时需要注意,传入eglCreateWindowSurface()的参数是SurfaceTexture实例。
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() {
//创建一个EGL实例
EGL10 egl = (EGL10) EGLContext.getEGL();
//
EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
//初始化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];
//选择config创建opengl运行环境
egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config);
EGLConfig config = configs[0];
EGLContext context = egl.eglCreateContext(dpy, config,
EGL10.EGL_NO_CONTEXT, null);
//创建新的surface
EGLSurface surface = egl.eglCreateWindowSurface(dpy, config, mSurfaceTexture, null);
//将opengles环境设置为当前
egl.eglMakeCurrent(dpy, surface, surface, context);
//获取当前opengles画布
GL10 gl = (GL10)context.getGL();
mRunning = true;
while (mRunning) {
SystemClock.sleep(333);
synchronized (mSurfaceTexture) {
onRender(gl);
//显示绘制结果到屏幕上
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);
}
}
代码示例参考
https://github.com/sunjinbo/hiopengl/tree/master/app/src/main/java/com/hiopengl/android/graphics