溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Android中常見(jiàn)的圖形繪制方式有哪些

發(fā)布時(shí)間:2021-07-02 15:43:11 來(lái)源:億速云 閱讀:130 作者:chen 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要講解了“Android中常見(jiàn)的圖形繪制方式有哪些”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Android中常見(jiàn)的圖形繪制方式有哪些”吧!

目錄
  • 圖形繪制概述

  • View + Canvas

  • SurfaceView + Canvas

  • TextureView + Canvas

  • SurfaceView + OpenGL ES

  • GLSurfaceView + OpenGL ES

  • TextureView + OpenGL ES

  • 總結(jié)

圖形繪制概述

Android平臺(tái)提供豐富的官方控件給開(kāi)發(fā)者實(shí)現(xiàn)界面UI開(kāi)發(fā),但在實(shí)際業(yè)務(wù)中經(jīng)常會(huì)遇到各種各樣的定制化需求,這必須由開(kāi)發(fā)者通過(guò)自繪控件的方式來(lái)實(shí)現(xiàn)。通常Android提供了Canvas和OpenGL ES兩種方式來(lái)實(shí)現(xiàn),其中Canvas借助于Android底層的Skia 2D向量圖形處理函數(shù)庫(kù)來(lái)實(shí)現(xiàn)的。具體如何通過(guò)Canvas和OpenGL來(lái)繪制圖形呢?這必須依賴(lài)于Android提供的View類(lèi)來(lái)具體實(shí)現(xiàn),下面組合幾種常見(jiàn)的應(yīng)用方式,如下所示:

Canvas

  • View + Canvas

  • SurfaceView + Canvas

  • TextureView + Canvas

OpenGL ES

  • SurfaceView + OpenGL ES

  • GLSurfaceView + OpenGL ES

  • TextureView + OpenGL ES

View + Canvas

這是一種通常使用的自繪控件方式,通過(guò)重寫(xiě)View類(lèi)的onDraw(Canvas canvas)方法實(shí)現(xiàn)。當(dāng)需要刷新繪制圖形時(shí),調(diào)用invalidate()方法讓View對(duì)象自身進(jìn)行刷新。該方案比較簡(jiǎn)單,涉及自定義邏輯較少,缺點(diǎn)是繪制邏輯在UI線(xiàn)程中進(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

這種方式相對(duì)于View + Canvas方式在于使用SurfaceView,因此會(huì)在Android的WMS系統(tǒng)上創(chuàng)建一塊自己的Surface進(jìn)行渲染繪制,其繪制邏輯可以在獨(dú)立的線(xiàn)程中進(jìn)行,因此性能相對(duì)于View + Canvas方式更高效。但通常情況下需要?jiǎng)?chuàng)建一個(gè)繪制線(xiàn)程,以及實(shí)現(xiàn)SurfaceHolder.Callback接口來(lái)管理SurfaceView的生命周期,其實(shí)現(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方式有些類(lèi)似,但由于它是通過(guò)TextureView來(lái)實(shí)現(xiàn)的,所以可以摒棄Surface不在View hierachy中缺陷,TextureView不會(huì)在WMS中單獨(dú)創(chuàng)建窗口,而是作為View hierachy中的一個(gè)普通View,因此可以和其它普通View一樣進(jìn)行移動(dòng),旋轉(zhuǎn),縮放,動(dòng)畫(huà)等變化。這種方式也有自身缺點(diǎn),它必須在硬件加速的窗口中才能使用,占用內(nèi)存比SurfaceView要高,在5.0以前在主UI線(xiàn)程渲染,5.0以后有單獨(dú)的渲染線(xiàn)程。

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圖形渲染常見(jiàn)的方式,如果想要進(jìn)行3D圖形渲染或者是高級(jí)圖像處理(比如濾鏡、AR等效果),就必須得引入OpenGL ES來(lái)實(shí)現(xiàn)了。OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三維圖形 API 的子集,針對(duì)手機(jī)、PDA和游戲主機(jī)等嵌入式設(shè)備而設(shè)計(jì),是一種圖形渲染API的設(shè)計(jì)標(biāo)準(zhǔn),不同的軟硬件開(kāi)發(fā)商在OpenGL API內(nèi)部可能會(huì)有不同的實(shí)現(xiàn)方式。

下面介紹一下在Android平臺(tái)上,如何進(jìn)行OpenGL ES渲染繪制,通常有以下三種方式:

SurfaceView + OpenGL ES

EGL是OpenGL API和原生窗口系統(tǒng)之間的接口,OpenGL ES 的平臺(tái)無(wú)關(guān)性正是借助 EGL 實(shí)現(xiàn)的,EGL 屏蔽了不同平臺(tái)的差異。如果使用OpenGL API來(lái)繪制圖形就必須先構(gòu)建EGL環(huán)境。

通常使用 EGL 渲染的一般步驟:

- 獲取 EGLDisplay對(duì)象,建立與本地窗口系統(tǒng)的連接調(diào)用eglGetDisplay方法得到EGLDisplay。

- 初始化EGL方法,打開(kāi)連接之后,調(diào)用eglInitialize方法初始化。

- 獲取EGLConfig對(duì)象,確定渲染表面的配置信息調(diào)用eglChooseConfig方法得到 EGLConfig。

- 創(chuàng)建渲染表面EGLSurface通過(guò)EGLDisplay和EGLConfig,調(diào)用eglCreateWindowSurface或eglCreatePbufferSurface方法創(chuàng)建渲染表面得到EGLSurface。

- 創(chuàng)建渲染上下文EGLContext通過(guò)EGLDisplay和EGLConfig,調(diào)用eglCreateContext方法創(chuàng)建渲染上下文,得到EGLContext。

- 綁定上下文通過(guò)eglMakeCurrent 方法將 EGLSurface、EGLContext、EGLDisplay 三者綁定,綁定成功之后OpenGLES環(huán)境就創(chuàng)建好了,接下來(lái)便可以進(jìn)行渲染。

- 交換緩沖OpenGLES 繪制結(jié)束后,使用eglSwapBuffers方法交換前后緩沖,將繪制內(nèi)容顯示到屏幕上,而屏幕外的渲染不需要調(diào)用此方法。

- 釋放EGL環(huán)境繪制結(jié)束后,不再需要使用EGL時(shí),需要取消eglMakeCurrent的綁定,銷(xiāo)毀 EGLDisplay、EGLSurface、EGLContext三個(gè)對(duì)象。

以上EGL環(huán)境構(gòu)建比較復(fù)雜,這里先不做過(guò)多解釋?zhuān)旅婵梢酝ㄟ^(guò)代碼參考其具體實(shí)現(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)建一個(gè)EGL實(shí)例
        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畫(huà)布
        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);
    }
}

從上面的代碼可以看到,相對(duì)于SurfaceView + Canvas的繪制方式,主要有以下兩點(diǎn)變化:

  • 在while(true)循環(huán)前后增加了EGL環(huán)境構(gòu)造的代碼

  • onRender()方法內(nèi)參數(shù)用的是GL10而不是Canvas

GLSurfaceView + OpenGL ES

由于構(gòu)建EGL環(huán)境比較繁瑣,以及還需要健壯地維護(hù)一個(gè)線(xiàn)程,直接使用SurfaceView進(jìn)行OpenGL繪制并不方便。幸好Android平臺(tái)提供GLSurfaceView類(lèi),很好地封裝了這些邏輯,使開(kāi)發(fā)者能夠快速地進(jìn)行OpenGL的渲染開(kāi)發(fā)。要使用GLSurfaceView類(lèi)進(jìn)行圖形渲染,需要實(shí)現(xiàn)GLSurfaceView.Renderer接口,該接口提供一個(gè)onDrawFrame(GL10 gl)方法,在該方法內(nèi)實(shí)現(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使用方法比較類(lèi)似,使用該方法有個(gè)好處是它是通過(guò)TextureView來(lái)實(shí)現(xiàn)的,所以可以摒棄Surface不在View hierachy中缺陷,TextureView不會(huì)在WMS中單獨(dú)創(chuàng)建窗口,而是作為View hierachy中的一個(gè)普通View,因此可以和其它普通View一樣進(jìn)行移動(dòng),旋轉(zhuǎn),縮放,動(dòng)畫(huà)等變化。這里使用TextureView類(lèi)在構(gòu)建EGL環(huán)境時(shí)需要注意,傳入eglCreateWindowSurface()的參數(shù)是SurfaceTexture實(shí)例。

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)建一個(gè)EGL實(shí)例
        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畫(huà)布
        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);
    }
}

代碼示例參考

github.com/sunjinbo/hi…

感謝各位的閱讀,以上就是“Android中常見(jiàn)的圖形繪制方式有哪些”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Android中常見(jiàn)的圖形繪制方式有哪些這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI