Android中常見的圖形繪制方式總結(jié)
圖形繪制概述
Android平臺提供豐富的官方控件給開發(fā)者實現(xiàn)界面UI開發(fā),但在實際業(yè)務(wù)中經(jīng)常會遇到各種各樣的定制化需求,這必須由開發(fā)者通過自繪控件的方式來實現(xiàn)。通常Android提供了Canvas和OpenGL ES兩種方式來實現(xiàn),其中Canvas借助于Android底層的Skia 2D向量圖形處理函數(shù)庫來實現(xiàn)的。具體如何通過Canvas和OpenGL來繪制圖形呢?這必須依賴于Android提供的View類來具體實現(xiàn),下面組合幾種常見的應(yīng)用方式,如下所示:
Canvas
- View + Canvas
- SurfaceView + Canvas
- TextureView + Canvas
OpenGL ES
- SurfaceView + OpenGL ES
- GLSurfaceView + OpenGL ES
- TextureView + OpenGL ES
View + Canvas
這是一種通常使用的自繪控件方式,通過重寫View類的onDraw(Canvas canvas)方法實現(xiàn)。當(dāng)需要刷新繪制圖形時,調(diào)用invalidate()方法讓View對象自身進(jìn)行刷新。該方案比較簡單,涉及自定義邏輯較少,缺點是繪制邏輯在UI線程中進(jìn)行,刷新效率不高,且不支持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系統(tǒng)上創(chuàng)建一塊自己的Surface進(jìn)行渲染繪制,其繪制邏輯可以在獨立的線程中進(jìn)行,因此性能相對于View + Canvas方式更高效。但通常情況下需要創(chuàng)建一個繪制線程,以及實現(xiàn)SurfaceHolder.Callback接口來管理SurfaceView的生命周期,其實現(xiàn)邏輯相比View + Canvas略復(fù)雜。另外它依然不支持3D渲染,且Surface因不在View hierachy中,它的顯示也不受View的屬性控制,所以不能進(jìn)行平移,縮放等變換,也不能放在其它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來實現(xiàn)的,所以可以摒棄Surface不在View hierachy中缺陷,TextureView不會在WMS中單獨創(chuàng)建窗口,而是作為View hierachy中的一個普通View,因此可以和其它普通View一樣進(jìn)行移動,旋轉(zhuǎn),縮放,動畫等變化。這種方式也有自身缺點,它必須在硬件加速的窗口中才能使用,占用內(nèi)存比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圖形渲染常見的方式,如果想要進(jìn)行3D圖形渲染或者是高級圖像處理(比如濾鏡、AR等效果),就必須得引入OpenGL ES來實現(xiàn)了。OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三維圖形 API 的子集,針對手機(jī)、PDA和游戲主機(jī)等嵌入式設(shè)備而設(shè)計,是一種圖形渲染API的設(shè)計標(biāo)準(zhǔn),不同的軟硬件開發(fā)商在OpenGL API內(nèi)部可能會有不同的實現(xiàn)方式。
下面介紹一下在Android平臺上,如何進(jìn)行OpenGL ES渲染繪制,通常有以下三種方式:
SurfaceView + OpenGL ES
EGL是OpenGL API和原生窗口系統(tǒng)之間的接口,OpenGL ES 的平臺無關(guān)性正是借助 EGL 實現(xiàn)的,EGL 屏蔽了不同平臺的差異。如果使用OpenGL API來繪制圖形就必須先構(gòu)建EGL環(huán)境。
通常使用 EGL 渲染的一般步驟:
- 獲取 EGLDisplay對象,建立與本地窗口系統(tǒng)的連接調(diào)用eglGetDisplay方法得到EGLDisplay。
- 初始化EGL方法,打開連接之后,調(diào)用eglInitialize方法初始化。
- 獲取EGLConfig對象,確定渲染表面的配置信息調(diào)用eglChooseConfig方法得到 EGLConfig。
- 創(chuàng)建渲染表面EGLSurface通過EGLDisplay和EGLConfig,調(diào)用eglCreateWindowSurface或eglCreatePbufferSurface方法創(chuàng)建渲染表面得到EGLSurface。
- 創(chuàng)建渲染上下文EGLContext通過EGLDisplay和EGLConfig,調(diào)用eglCreateContext方法創(chuàng)建渲染上下文,得到EGLContext。
- 綁定上下文通過eglMakeCurrent 方法將 EGLSurface、EGLContext、EGLDisplay 三者綁定,綁定成功之后OpenGLES環(huán)境就創(chuàng)建好了,接下來便可以進(jìn)行渲染。
- 交換緩沖OpenGLES 繪制結(jié)束后,使用eglSwapBuffers方法交換前后緩沖,將繪制內(nèi)容顯示到屏幕上,而屏幕外的渲染不需要調(diào)用此方法。
- 釋放EGL環(huán)境繪制結(jié)束后,不再需要使用EGL時,需要取消eglMakeCurrent的綁定,銷毀 EGLDisplay、EGLSurface、EGLContext三個對象。
以上EGL環(huán)境構(gòu)建比較復(fù)雜,這里先不做過多解釋,下面可以通過代碼參考其具體實現(xiàn):
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() { //創(chuàng)建一個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創(chuàng)建opengl運(yùn)行環(huán)境 egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config); EGLConfig config = configs[0]; EGLContext context = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, null); //創(chuàng)建新的surface EGLSurface surface = egl.eglCreateWindowSurface(dpy, config, mSurfaceHolder, null); //將opengles環(huán)境設(shè)置為當(dāng)前 egl.eglMakeCurrent(dpy, surface, surface, context); //獲取當(dāng)前opengles畫布 GL10 gl = (GL10)context.getGL(); mRunning = true; while (mRunning) { SystemClock.sleep(333); synchronized (mSurfaceHolder) { onRender(gl); //顯示繪制結(jié)果到屏幕上 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)循環(huán)前后增加了EGL環(huán)境構(gòu)造的代碼
- onRender()方法內(nèi)參數(shù)用的是GL10而不是Canvas
GLSurfaceView + OpenGL ES
由于構(gòu)建EGL環(huán)境比較繁瑣,以及還需要健壯地維護(hù)一個線程,直接使用SurfaceView進(jìn)行OpenGL繪制并不方便。幸好Android平臺提供GLSurfaceView類,很好地封裝了這些邏輯,使開發(fā)者能夠快速地進(jìn)行OpenGL的渲染開發(fā)。要使用GLSurfaceView類進(jìn)行圖形渲染,需要實現(xiàn)GLSurfaceView.Renderer接口,該接口提供一個onDrawFrame(GL10 gl)方法,在該方法內(nèi)實現(xiàn)具體的渲染邏輯。
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來實現(xiàn)的,所以可以摒棄Surface不在View hierachy中缺陷,TextureView不會在WMS中單獨創(chuàng)建窗口,而是作為View hierachy中的一個普通View,因此可以和其它普通View一樣進(jìn)行移動,旋轉(zhuǎn),縮放,動畫等變化。這里使用TextureView類在構(gòu)建EGL環(huán)境時需要注意,傳入eglCreateWindowSurface()的參數(shù)是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() { //創(chuàng)建一個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創(chuàng)建opengl運(yùn)行環(huán)境 egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config); EGLConfig config = configs[0]; EGLContext context = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, null); //創(chuàng)建新的surface EGLSurface surface = egl.eglCreateWindowSurface(dpy, config, mSurfaceTexture, null); //將opengles環(huán)境設(shè)置為當(dāng)前 egl.eglMakeCurrent(dpy, surface, surface, context); //獲取當(dāng)前opengles畫布 GL10 gl = (GL10)context.getGL(); mRunning = true; while (mRunning) { SystemClock.sleep(333); synchronized (mSurfaceTexture) { onRender(gl); //顯示繪制結(jié)果到屏幕上 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); } }
代碼示例參考
總結(jié)
到此這篇關(guān)于Android中常見圖形繪制方式的文章就介紹到這了,更多相關(guān)Android圖形繪制方式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android帶圓形數(shù)字進(jìn)度的自定義進(jìn)度條示例
本篇文章主要介紹了Android帶圓形數(shù)字進(jìn)度的自定義進(jìn)度條示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02詳解Flutter WebView與JS互相調(diào)用簡易指南
這篇文章主要介紹了詳解Flutter WebView與JS互相調(diào)用簡易指南,分為JS調(diào)用Flutter和Flutter調(diào)用JS,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-04-04Android ContentProvider實現(xiàn)獲取手機(jī)聯(lián)系人功能
這篇文章主要為大家詳細(xì)介紹了Android ContentProvider實現(xiàn)獲取手機(jī)聯(lián)系人功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07Android RecyclerView顯示Item布局不一致解決辦法
這篇文章主要介紹了Android RecyclerView顯示Item布局不一致解決辦法的相關(guān)資料,需要的朋友可以參考下2017-07-07Android 啟動另一個App/apk中的Activity實現(xiàn)代碼
這篇文章主要介紹了Android 啟動另一個App/apk中的Activity實現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-04-04限時搶購秒殺系統(tǒng)架構(gòu)分析與實戰(zhàn)
這篇文章主要介紹了限時搶購秒殺系統(tǒng)架構(gòu)分析與實戰(zhàn) 的相關(guān)資料,需要的朋友可以參考下2016-01-01Android實現(xiàn)GridView中ImageView動態(tài)變換的方法
這篇文章主要介紹了Android實現(xiàn)GridView中ImageView動態(tài)變換的方法,以實例形式較為詳細(xì)的分析了GridView中ImageView動態(tài)變換的頁面布局及功能實現(xiàn)相關(guān)技巧,需要的朋友可以參考下2015-10-10Kotlin 協(xié)程與掛起函數(shù)及suspend關(guān)鍵字深入理解
這篇文章主要為大家介紹了Kotlin 協(xié)程與掛起函數(shù)及suspend關(guān)鍵字深入理解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12