這篇文章主要講解了“Android中常見的圖形繪制方式有哪些”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Android中常見的圖形繪制方式有哪些”吧!
圖形繪制概述
View + Canvas
SurfaceView + Canvas
TextureView + Canvas
SurfaceView + OpenGL ES
GLSurfaceView + OpenGL ES
TextureView + OpenGL ES
總結
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類的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.
}
}這種方式相對于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.
}
}該方式同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渲染繪制,通常有以下三種方式:
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
由于構建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);
}
}該方式跟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);
}
}代碼示例參考
github.com/sunjinbo/hi…
感謝各位的閱讀,以上就是“Android中常見的圖形繪制方式有哪些”的內容了,經過本文的學習后,相信大家對Android中常見的圖形繪制方式有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。