安卓surfaceview的使用方式

1. 什么是surfaceview

surfaceview內(nèi)部機制和外部層次結(jié)構

在安卓開發(fā)中腰奋,我們經(jīng)常會遇到一些需要高性能、高幀率抱怔、高畫質(zhì)的應用場景劣坊,例如視頻播放、游戲開發(fā)屈留、相機預覽等局冰。這些場景中,我們需要直接操作圖像數(shù)據(jù)灌危,并且實時地顯示到屏幕上康二。如果我們使用普通的view組件來實現(xiàn)這些功能,可能會遇到以下問題:

  • view組件是在主線程中進行繪制的乍狐,如果繪制過程耗時過長或者頻繁刷新赠摇,可能會導致主線程阻塞,影響用戶交互和界面響應。
  • view組件在繪制時沒有使用雙緩沖機制藕帜,也就是說每次繪制都是直接在屏幕上進行的烫罩,這可能會導致繪制過程中出現(xiàn)閃爍或者撕裂的現(xiàn)象。
  • view組件是基于view層次結(jié)構的洽故,也就是說每個view都是一個矩形區(qū)域贝攒,如果我們想要實現(xiàn)一些不規(guī)則形狀或者透明度變化的效果,可能會比較困難时甚。

為了解決這些問題隘弊,安卓提供了一種特殊的view組件:surfaceview 。surfaceview擁有自己獨立的surface荒适,也就是一個可以在其上直接繪制內(nèi)容的圖形緩沖區(qū)梨熙。surfaceview的內(nèi)容是透明的,可以嵌入到view層次結(jié)構中刀诬,并且可以和其他view進行重疊或者裁剪咽扇。surfaceview適用于需要頻繁刷新或處理邏輯復雜的繪圖場景,如視頻播放陕壹、游戲等质欲。

下圖展示了surfaceview和普通view在屏幕上的顯示效果:

surfaceview和普通view

從圖中可以看出,普通view是按照順序依次繪制到屏幕上的糠馆,而surfaceview則是直接繪制到屏幕上的一個透明區(qū)域嘶伟,并且可以和其他view進行重疊或者裁剪。

2. surfaceview和view的區(qū)別

從上面的介紹中又碌,我們已經(jīng)了解了surfaceview和普通view在顯示效果上的區(qū)別九昧。那么,在實現(xiàn)原理和使用方式上毕匀,它們又有什么不同呢耽装?下面我們來對比一下它們的主要區(qū)別:

特點 普通view surfaceview
更新方式 主動更新,可以在任何時候調(diào)用invalidate方法來觸發(fā)重繪期揪,在onDraw方法中使用canvas進行繪制 被動更新掉奄,不能直接控制重繪,需要通過一個子線程來進行頁面的刷新凤薛,在子線程中直接操作surface進行繪制
刷新線程 主線程刷新姓建,可以保證界面的一致性和同步性,但是可能導致主線程阻塞或者掉幀 子線程刷新缤苫,可以避免主線程阻塞速兔,并且可以提高刷新頻率和效率,但是需要注意線程間的通信和同步問題
緩沖機制 無雙緩沖機制活玲,每次繪制都是直接在屏幕上進行涣狗,可以節(jié)省內(nèi)存空間谍婉,但是可能導致閃爍或者撕裂的現(xiàn)象 有雙緩沖機制,每次繪制都是先在一個緩沖區(qū)中進行镀钓,然后再將緩沖區(qū)中的內(nèi)容復制到屏幕上穗熬,可以避免閃爍或者撕裂的現(xiàn)象,并且可以提高繪制質(zhì)量丁溅,但是需要消耗更多的內(nèi)存空間
  • 更新方式:普通view適用于主動更新的情況唤蔗,也就是說我們可以在任何時候調(diào)用view的invalidate方法來觸發(fā)view的重繪,然后在onDraw方法中使用canvas進行繪制窟赏。而surfaceview主要用于被動更新的情況妓柜,也就是說我們不能直接控制surfaceview的重繪,而是需要通過一個子線程來進行頁面的刷新涯穷,然后在子線程中直接操作surface進行繪制棍掐。
  • 刷新線程:普通view是在主線程里面進行刷新的,也就是說所有的繪制操作都是在主線程中完成的拷况。這樣的好處是可以保證界面的一致性和同步性塌衰,但是也有可能導致主線程阻塞或者掉幀。而surfaceview是通過一個子線程來進行頁面的刷新的蝠嘉,也就是說所有的繪制操作都是在子線程中完成的。這樣的好處是可以避免主線程阻塞杯巨,并且可以提高刷新頻率和效率蚤告,但是也需要注意線程間的通信和同步問題。
  • 緩沖機制:普通view在繪圖時沒有使用雙緩沖機制服爷,也就是說每次繪制都是直接在屏幕上進行的杜恰。這樣的好處是可以節(jié)省內(nèi)存空間,但是也可能導致繪制過程中出現(xiàn)閃爍或者撕裂的現(xiàn)象仍源。而surfaceview在底層實現(xiàn)機制中已經(jīng)實現(xiàn)了雙緩沖機制心褐,也就是說每次繪制都是先在一個緩沖區(qū)中進行,然后再將緩沖區(qū)中的內(nèi)容復制到屏幕上笼踩。這樣的好處是可以避免閃爍或者撕裂的現(xiàn)象逗爹,并且可以提高繪制質(zhì)量,但是也需要消耗更多的內(nèi)存空間嚎于。

3. surfaceview的創(chuàng)建和使用

了解了surfaceview和普通view的區(qū)別之后掘而,我們就可以開始創(chuàng)建和使用surfaceview了。創(chuàng)建自定義的surfaceview需要以下幾個步驟:

  • 繼承surfaceview:首先于购,我們需要創(chuàng)建一個自定義的類袍睡,繼承自surfaceview,并實現(xiàn)兩個接口:surfaceholder.callback和runnable肋僧。前者用于監(jiān)聽surface的狀態(tài)變化斑胜,后者用于實現(xiàn)子線程的邏輯控淡。
  • 初始化surfaceholder:其次,我們需要在構造方法中初始化surfaceholder對象止潘,并注冊surfaceholder的回調(diào)方法掺炭。surfaceholder是一個用于管理surface的類,它提供了一些方法來獲取和操作surface覆山。
  • 處理回調(diào)方法:然后竹伸,我們需要在回調(diào)方法中處理surface的創(chuàng)建、改變和銷毀事件簇宽。當surface被創(chuàng)建時勋篓,我們需要啟動子線程,并根據(jù)需要調(diào)整view的大小或位置魏割;當surface被改變時譬嚣,我們需要重新獲取surface的寬高,并根據(jù)需要調(diào)整view的大小或位置钞它;當surface被銷毀時拜银,我們需要停止子線程,并釋放相關資源遭垛。
  • 實現(xiàn)run方法:接著尼桶,我們需要在run方法中實現(xiàn)子線程的繪圖邏輯。我們可以使用一個循環(huán)來不斷地刷新頁面锯仪,并且根據(jù)不同的條件來控制循環(huán)的退出泵督。
  • 獲取canvas對象:最后,我們需要在draw方法中獲取canvas對象庶喜,并通過lockcanvas和unlockcanvasandpost方法進行繪圖操作小腊。lockcanvas方法會返回一個canvas對象,我們可以使用它來對surface進行繪制久窟;unlockcanvasandpost方法會將繪制好的內(nèi)容顯示到屏幕上秩冈,并且釋放canvas對象。

下面給出一個簡單的示例代碼斥扛,實現(xiàn)了一個簡單的畫板功能:

//自定義類繼承自SurfaceView入问,并實現(xiàn)SurfaceHolder.Callback和Runnable接口
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    //聲明SurfaceHolder對象
    private SurfaceHolder mHolder;
    //聲明子線程對象
    private Thread mThread;
    //聲明畫筆對象
    private Paint mPaint;
    //聲明畫布對象
    private Canvas mCanvas;
    //聲明一個標志位,用于控制子線程的退出
    private boolean mIsDrawing;

    //構造方法稀颁,初始化相關對象
    public MySurfaceView(Context context) {
        super(context);
        //獲取SurfaceHolder對象
        mHolder = getHolder();
        //注冊SurfaceHolder的回調(diào)方法
        mHolder.addCallback(this);
        //初始化畫筆對象队他,設置顏色和寬度
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(10);
    }

    //當Surface被創(chuàng)建時,啟動子線程峻村,并根據(jù)需要調(diào)整View的大小或位置
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        //設置標志位為true麸折,表示子線程可以開始運行
        mIsDrawing = true;
        //創(chuàng)建并啟動子線程
        mThread = new Thread(this);
        mThread.start();
    }

    //當Surface被改變時,重新獲取Surface的寬高粘昨,并根據(jù)需要調(diào)整View的大小或位置
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        //TODO: 根據(jù)需要調(diào)整View的大小或位置
    }

    //當Surface被銷毀時垢啼,停止子線程窜锯,并釋放相關資源
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        //設置標志位為false,表示子線程可以停止運行
        mIsDrawing = false;
        try {
            //等待子線程結(jié)束芭析,并釋放子線程對象
            mThread.join();
            mThread = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //實現(xiàn)run方法锚扎,實現(xiàn)子線程的繪圖邏輯
    @Override
    public void run() {
        //使用一個循環(huán)來不斷地刷新頁面
        while (mIsDrawing) {
            //獲取當前時間,用于計算繪制時間
            long start = System.currentTimeMillis();
            //調(diào)用draw方法進行繪制操作
            draw();
            //獲取結(jié)束時間馁启,用于計算繪制時間
            long end = System.currentTimeMillis();
            //如果繪制時間小于16ms驾孔,則延時一段時間,保證每秒60幀的刷新率
            if (end - start < 16) {
                try {
                    Thread.sleep(16 - (end - start));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //獲取canvas對象惯疙,并通過lockCanvas和unlockCanvasAndPost方法進行繪制操作
    private void draw() {
        try {
            //通過lockCanvas方法獲取canvas對象翠勉,如果surface不可用,則返回null
            mCanvas = mHolder.lockCanvas();
            if (mCanvas != null) {
                //TODO: 在canvas上進行繪制操作霉颠,例如畫線对碌、畫圓、畫文字等

                //在本例中蒿偎,我們簡單地使用隨機數(shù)生成一些坐標點朽们,并用畫筆連接它們,形成一條折線圖

                //生成一個隨機數(shù)對象
                Random random = new Random();
                //生成一個點的集合诉位,用于存儲坐標點
                List<Point> points = new ArrayList<>();
                //循環(huán)生成10個隨機坐標點骑脱,并添加到集合中
                for (int i = 0; i < 10; i++) {
                    int x = random.nextInt(mCanvas.getWidth());
                    int y = random.nextInt(mCanvas.getHeight());
                    points.add(new Point(x, y));
                }
                //遍歷點的集合,用畫筆連接相鄰的兩個點苍糠,形成一條折線圖
                for (int i = 0; i < points.size() - 1; i++) {
                    Point p1 = points.get(i);
                    Point p2 = points.get(i + 1);
                    mCanvas.drawLine(p1.x, p1.y, p2.x, p2.y, mPaint);
                }

                //通過unlockCanvasAndPost方法將繪制好的內(nèi)容顯示到屏幕上叁丧,并釋放canvas對象
                mHolder.unlockCanvasAndPost(mCanvas);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

下圖展示了上述代碼運行的效果:


簡單的畫板

從圖中可以看出,我們在surface上繪制了一條隨機的折線圖椿息,并且顯示到了屏幕上。這只是一個簡單的示例坷衍,我們可以根據(jù)自己的需求寝优,實現(xiàn)更復雜的繪圖邏輯和效果。

4. surfaceview和activity的生命周期

在使用surfaceview時枫耳,我們需要注意它和activity的生命周期之間的關系乏矾。因為surfaceview是嵌入到view層次結(jié)構中的,所以它會受到activity的生命周期的影響迁杨。但是钻心,surfaceview也有自己的生命周期,它是由surfaceholder來管理的铅协。因此捷沸,對于具有surfaceview的activity,存在兩個單獨但相互依賴的狀態(tài)機:應用oncreate/onresume/onpause和已創(chuàng)建/更改/銷毀的surface狐史。

下圖展示了這兩個狀態(tài)機之間的關系:

surfaceview和activity的狀態(tài)機之間的關系
surfaceview和activity的生命周期

從圖中可以看出痒给,當activity被創(chuàng)建時说墨,會觸發(fā)surfaceview的創(chuàng)建;當activity被恢復時苍柏,會觸發(fā)surfaceview的改變尼斧;當activity被暫停時,會觸發(fā)surfaceview的銷毀试吁。因此棺棵,在這些事件中,我們需要做一些相應的處理熄捍,例如:

  • 啟動/停止子線程:當surface被創(chuàng)建或者銷毀時烛恤,我們需要啟動或者停止子線程,并根據(jù)需要調(diào)整view的大小或位置治唤。如果我們不及時地啟動或者停止子線程棒动,可能會導致內(nèi)存泄漏或者空指針異常。
  • 保存/恢復狀態(tài):當activity被暫停時宾添,我們需要從子線程中提取狀態(tài)船惨,并保存到bundle中;當activity被恢復時缕陕,我們需要從bundle中恢復狀態(tài)粱锐,并傳遞給子線程。如果我們不及時地保存或者恢復狀態(tài)扛邑,可能會導致數(shù)據(jù)丟失或者不一致怜浅。

5. surfaceview和glsurfaceview

在上面的內(nèi)容中,我們介紹了如何使用surfaceview來實現(xiàn)一些高性能蔬崩、高幀率恶座、高畫質(zhì)的應用。但是沥阳,如果我們想要實現(xiàn)一些更加復雜和精美的3D圖形效果跨琳,例如光照、陰影桐罕、紋理脉让、動畫等,那么我們就需要使用opengl es來進行渲染功炮。opengl es是一種跨平臺的圖形庫溅潜,它可以利用gpu加速來提高渲染效率。

為了方便我們使用opengl es進行渲染薪伏,安卓提供了一種專門用于渲染opengl es內(nèi)容的surfaceview:glsurfaceview 滚澜。glsurfaceview是一種繼承自surfaceview的組件,它在底層封裝了egl上下文嫁怀、線程間通信以及與activity生命周期交互等功能博秫。使用glsurfaceview時潦牛,我們無需自己創(chuàng)建和管理子線程,只需實現(xiàn)glsurfaceview.renderer接口挡育,并設置給glsurfaceview對象即可巴碗。

下圖展示了glsurfaceview和普通surfaceview在屏幕上的顯示效果:

glsurfaceview和普通surfaceview

從圖中可以看出,glsurfaceview和普通surfaceview都是直接繪制到屏幕上的一個透明區(qū)域即寒,但是glsurfaceview可以使用opengl es來繪制一些更加復雜和精美的3D圖形效果橡淆。

6. glsurfaceview的創(chuàng)建和使用

了解了glsurfaceview和普通surfaceview的區(qū)別之后,我們就可以開始創(chuàng)建和使用glsurfaceview了母赵。創(chuàng)建自定義的glsurfaceview需要以下幾個步驟:

  • 繼承glsurfaceview:首先逸爵,我們需要創(chuàng)建一個自定義的類,繼承自glsurfaceview凹嘲,并在構造方法中初始化相關對象师倔。
  • 設置渲染器:其次,我們需要實現(xiàn)glsurfaceview.renderer接口周蹭,并設置給glsurfaceview對象趋艘。渲染器是一個用于繪制opengl es內(nèi)容的類,它提供了三個方法:onSurfaceCreated凶朗、onSurfaceChanged和onDrawFrame瓷胧。
  • 設置渲染模式:然后,我們需要設置glsurfaceview的渲染模式棚愤,有兩種可選:RENDERMODE_CONTINUOUSLY和RENDERMODE_WHEN_DIRTY搓萧。前者表示持續(xù)地刷新頁面,后者表示只有在調(diào)用requestRender方法時才刷新頁面宛畦。
  • 獲取opengl es對象:最后瘸洛,我們需要在渲染器的方法中獲取opengl es對象,并使用它來進行繪制操作次和。opengl es對象是一個用于操作圖形數(shù)據(jù)的類微王,它提供了一系列的方法來創(chuàng)建施蜜、加載赦邻、繪制江兢、變換喧锦、釋放等圖形資源读规。

下面給出一個簡單的示例代碼,實現(xiàn)了一個簡單的3D立方體效果:

//自定義類繼承自GLSurfaceView燃少,并在構造方法中初始化相關對象
public class MyGLSurfaceView extends GLSurfaceView {

    //聲明渲染器對象
    private MyRenderer mRenderer;

    //構造方法束亏,初始化相關對象
    public MyGLSurfaceView(Context context) {
        super(context);
        //設置opengl es版本為2.0
        setEGLContextClientVersion(2);
        //創(chuàng)建并設置渲染器對象
        mRenderer = new MyRenderer();
        setRenderer(mRenderer);
        //設置渲染模式為持續(xù)刷新
        setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }

    //自定義類實現(xiàn)GLSurfaceView.Renderer接口,并實現(xiàn)三個方法
    private class MyRenderer implements GLSurfaceView.Renderer {

        //聲明opengl es對象
        private GLES20 gl;

        //聲明頂點著色器代碼
        private final String vertexShaderCode =
                "attribute vec4 vPosition;" +
                "uniform mat4 uMVPMatrix;" +
                "void main() {" +
                "  gl_Position = uMVPMatrix * vPosition;" +
                "}";

        //聲明片元著色器代碼
        private final String fragmentShaderCode =
                "precision mediump float;" +
                "uniform vec4 vColor;" +
                "void main() {" +
                "  gl_FragColor = vColor;" +
                "}";

        //聲明頂點坐標數(shù)組
        private final float[] vertexCoords = {
                -0.5f, -0.5f, -0.5f,   // front bottom left
                0.5f, -0.5f, -0.5f,   // front bottom right
                0.5f,  0.5f, -0.5f,   // front top right
                -0.5f,  0.5f, -0.5f,  // front top left
                -0.5f, -0.5f,  0.5f,   // back bottom left
                0.5f, -0.5f,  0.5f,   // back bottom right
                0.5f,  0.5f,  0.5f,   // back top right
                -0.5f,  0.5f,  0.5f   // back top left
        };

        //聲明頂點索引數(shù)組
        private final short[] drawOrder = {
                0, 1, 2,   // front face
                0, 2, 3,
                4, 5, 6,   // back face
                4, 6, 7,
                0, 4, 7,   // left face
                0, 7, 3,
                1, 5, 6,   // right face
                1, 6, 2,
                3, 2, 6,   // top face
                3, 6, 7,
                0, 1, 5,   // bottom face
                0, 5, 4
        };

        //聲明顏色數(shù)組
        private final float[] colors = {
                1.0f, 0.0f, 0.0f, 1.0f, // red
                0.0f, 1.0f, 0.0f, 1.0f, // green
                0.0f, 0.0f, 1.0f, 1.0f, // blue
                1.0f, 1.0f, 0.0f, 1.0f, // yellow
                1.0f, 0.0f, 1.0f, 1.0f, // magenta
                0.0f, 1.0f, 1.0f, 1.0f // cyan
        };

        //聲明頂點緩沖對象
        private FloatBuffer vertexBuffer;
        //聲明索引緩沖對象
        private ShortBuffer drawListBuffer;
        //聲明顏色緩沖對象
        private FloatBuffer colorBuffer;

        //聲明頂點著色器對象
        private int vertexShader;
        //聲明片元著色器對象
        private int fragmentShader;
        //聲明程序?qū)ο?        private int program;

        //聲明頂點位置屬性的句柄
        private int positionHandle;
        //聲明顏色屬性的句柄
        private int colorHandle;
        //聲明投影矩陣屬性的句柄
        private int mvpMatrixHandle;

        //聲明模型矩陣對象
        private float[] modelMatrix = new float[16];
        //聲明視圖矩陣對象
        private float[] viewMatrix = new float[16];
        //聲明投影矩陣對象
        private float[] projectionMatrix = new float[16];
        //聲明模型視圖投影矩陣對象
        private float[] mvpMatrix = new float[16];

        //當Surface被創(chuàng)建時阵具,初始化opengl es對象碍遍,并加載和編譯著色器定铜,創(chuàng)建和綁定圖形數(shù)據(jù),設置相機位置和投影方式等操作
        @Override
        public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
            //獲取opengl es對象怕敬,用于后續(xù)的繪制操作
            gl = (GLES20) gl10;

            //設置背景顏色為黑色揣炕,用于清除屏幕時使用
            gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

            //加載和編譯頂點著色器,返回一個句柄东跪,用于后續(xù)的鏈接操作
            vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
                    vertexShaderCode);

            //加載和編譯片元著色器畸陡,返回一個句柄,用于后續(xù)的鏈接操作
            fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);

            //創(chuàng)建一個空的程序?qū)ο笏涮睿祷匾粋€句柄丁恭,用于后續(xù)的鏈接操作
            program = GLES20.glCreateProgram();

            //將頂點著色器和片元著色器附加到程序?qū)ο笊?            GLES20.glAttachShader(program, vertexShader);
            GLES20.glAttachShader(program, fragmentShader);

            //鏈接程序?qū)ο螅勺罱K的可執(zhí)行程序
            GLES20.glLinkProgram(program);

            //使用程序?qū)ο笳眨せ钕嚓P的屬性和統(tǒng)一變量
            GLES20.glUseProgram(program);

            //獲取頂點位置屬性的句柄牲览,用于后續(xù)的綁定操作
            positionHandle = GLES20.glGetAttribLocation(program, "vPosition");

            //獲取顏色屬性的句柄,用于后續(xù)的綁定操作
            colorHandle = GLES20.glGetUniformLocation(program, "vColor");

            //獲取投影矩陣屬性的句柄恶守,用于后續(xù)的綁定操作
            mvpMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");

            //將頂點坐標數(shù)組轉(zhuǎn)換為字節(jié)緩沖對象第献,用于后續(xù)的傳輸操作
            ByteBuffer bb = ByteBuffer.allocateDirect(
                    vertexCoords.length * 4);
            bb.order(ByteOrder.nativeOrder());
            vertexBuffer = bb.asFloatBuffer();
            vertexBuffer.put(vertexCoords);
            vertexBuffer.position(0);

            //將頂點索引數(shù)組轉(zhuǎn)換為字節(jié)緩沖對象,用于后續(xù)的傳輸操作
            ByteBuffer dlb = ByteBuffer.allocateDirect(
                    drawOrder.length * 2);
            dlb.order(ByteOrder.nativeOrder());
            drawListBuffer = dlb.asShortBuffer();
            drawListBuffer.put(drawOrder);
            drawListBuffer.position(0);

            //將顏色數(shù)組轉(zhuǎn)換為字節(jié)緩沖對象熬的,用于后續(xù)的傳輸操作
            ByteBuffer cb = ByteBuffer.allocateDirect(
                    colors.length * 4);
            cb.order(ByteOrder.nativeOrder());
            colorBuffer = cb.asFloatBuffer();
            colorBuffer.put(colors);
            colorBuffer.position(0);

            //設置相機位置和朝向痊硕,生成視圖矩陣
            Matrix.setLookAtM(viewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

        }

        //當Surface被改變時,調(diào)整視口大小押框,并設置投影方式岔绸,生成投影矩陣
        @Override
        public void onSurfaceChanged(GL10 gl10, int width, int height) {
            //設置視口大小為Surface的大小
            GLES20.glViewport(0, 0, width, height);

            //設置投影方式為透視投影,并根據(jù)視口寬高比計算投影矩陣
            float ratio = (float) width / height;
            Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);

        }

        //當Surface被繪制時橡伞,清除屏幕盒揉,并旋轉(zhuǎn)模型矩陣,生成模型視圖投影矩陣兑徘,并傳輸和繪制圖形數(shù)據(jù)
        @Override
        public void onDrawFrame(GL10 gl10) {
            //清除屏幕顏色緩沖區(qū)
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

            //設置模型矩陣為單位矩陣刚盈,并根據(jù)系統(tǒng)時間旋轉(zhuǎn)模型矩陣
            Matrix.setIdentityM(modelMatrix, 0);
            Matrix.rotateM(modelMatrix, 0, (float) SystemClock.uptimeMillis() / 1000 * 30f, 1.0f, 1.0f, 1.0f);

            //將模型矩陣、視圖矩陣和投影矩陣相乘挂脑,生成模型視圖投影矩陣
            Matrix.multiplyMM(mvpMatrix, 0, viewMatrix, 0, modelMatrix, 0);
            Matrix.multiplyMM(mvpMatrix, 0, projectionMatrix, 0, mvpMatrix, 0);

            //將模型視圖投影矩陣傳輸?shù)巾旤c著色器中藕漱,并激活該屬性
            GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mvpMatrix, 0);

            //將頂點坐標數(shù)據(jù)傳輸?shù)巾旤c著色器中,并激活該屬性
            GLES20.glVertexAttribPointer(positionHandle, 3,
                    GLES20.GL_FLOAT, false,
                    0, vertexBuffer);
            GLES20.glEnableVertexAttribArray(positionHandle);

            //使用循環(huán)為每個面設置不同的顏色崭闲,并繪制三角形
            for (int i = 0; i < 6; i++) {
                //將顏色數(shù)據(jù)傳輸?shù)狡髦欣吡⒓せ钤搶傩?                colorBuffer.position(i * 4);
                GLES20.glUniform4fv(colorHandle, 1, colorBuffer);
                //繪制三角形,使用頂點索引數(shù)組來確定頂點的順序
                drawListBuffer.position(i * 6);
                GLES20.glDrawElements(
                        GLES20.GL_TRIANGLES, 6,
                        GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
            }

        }

        //定義一個加載和編譯著色器的方法刁俭,接收一個著色器類型和一個著色器代碼橄仍,返回一個著色器句柄
        public int loadShader(int type, String shaderCode) {

            //創(chuàng)建一個空的著色器對象,返回一個句柄,用于后續(xù)的編譯操作
            int shader = GLES20.glCreateShader(type);

            //將著色器代碼傳輸?shù)街鲗ο笾?            GLES20.glShaderSource(shader, shaderCode);

            //編譯著色器對象
            GLES20.glCompileShader(shader);

            //返回著色器句柄
            return shader;
        }
    }
}

glsurfaceview的使用方式

什么是glsurfaceview

glsurfaceview是一種專門用于渲染opengl es內(nèi)容的surfaceview侮繁。opengl es是一種用于嵌入式設備上的3D圖形渲染API虑粥。glsurfaceview類提供了用于管理egl上下文、在線程間通信以及與activity生命周期交互的輔助程序類宪哩。使用glsurfaceview時娩贷,無需自己創(chuàng)建和管理子線程,只需實現(xiàn)glsurfaceview.renderer接口锁孟,并設置給glsurfaceview對象即可育勺。

glsurfaceview和surfaceview的區(qū)別

glsurfaceview和surfaceview都是繼承自surfaceview的類,都可以在子線程中直接操作surface進行繪制罗岖。但是glsurfaceview相比surfaceview有以下的優(yōu)勢:

  • glsurfaceview可以自動創(chuàng)建和管理egl上下文涧至,無需自己處理egl的初始化、銷毀桑包、切換等操作南蓬。
  • glsurfaceview可以自動創(chuàng)建和管理子線程,無需自己處理線程的啟動哑了、停止赘方、同步等操作。
  • glsurfaceview可以自動處理與activity生命周期的交互弱左,無需自己處理activity的暫停窄陡、恢復、保存狀態(tài)等操作拆火。
  • glsurfaceview可以提供多種渲染模式跳夭,可以根據(jù)需要調(diào)整渲染頻率,避免過度繪制或掉幀们镜。

glsurfaceview的創(chuàng)建和使用

創(chuàng)建自定義的glsurfaceview繼承glsurfaceview币叹,并在構造方法中設置opengl es版本、渲染器對象和渲染模式模狭。創(chuàng)建自定義的渲染器實現(xiàn)glsurfaceview.renderer接口颈抚,并在回調(diào)方法中進行初始化、視口設置和繪圖操作嚼鹉。

以下是一個簡單的示例代碼:

// 自定義的glsurfaceview類
public class MyGLSurfaceView extends GLSurfaceView {

    // 構造方法
    public MyGLSurfaceView(Context context) {
        super(context);
        // 設置opengl es版本為2.0
        setEGLContextClientVersion(2);
        // 設置渲染器對象
        setRenderer(new MyRenderer());
        // 設置渲染模式為連續(xù)模式
        setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }

    // 自定義的渲染器類
    private class MyRenderer implements GLSurfaceView.Renderer {

        // 渲染器創(chuàng)建時的回調(diào)方法
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            // 在這里進行一些初始化操作贩汉,比如設置清屏顏色為黑色
            GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        }

        // 渲染器改變時的回調(diào)方法
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            // 在這里進行一些視口設置操作,比如設置視口大小為surface的大小
            GLES20.glViewport(0, 0, width, height);
        }

        // 渲染器繪制時的回調(diào)方法
        @Override
        public void onDrawFrame(GL10 gl) {
            // 在這里進行一些繪圖操作锚赤,比如清屏
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        }
    }
}

glsurfaceview和activity的生命周期

當使用glsurfaceview時匹舞,無需自己處理與activity生命周期的交互,glsurfaceview會自動根據(jù)activity的狀態(tài)來暫脱缡鳎或恢復渲染器策菜。但是如果需要保存或恢復一些重要的數(shù)據(jù)或狀態(tài)晶疼,可以在activity的onSaveInstanceState和onRestoreInstanceState方法中進行操作酒贬。

以下是一個示意圖又憨,展示了glsurfaceview和activity的生命周期之間的關系:


glsurfaceview和activity的生命周期

從圖中可以看出,當activity創(chuàng)建時锭吨,會觸發(fā)glsurfaceview的onSurfaceCreated回調(diào)方法蠢莺,這時會創(chuàng)建渲染器對象,并調(diào)用渲染器的onSurfaceCreated回調(diào)方法零如。當activity恢復時躏将,會觸發(fā)glsurfaceview的onDrawFrame回調(diào)方法,這時會恢復渲染器的繪制操作考蕾,并調(diào)用渲染器的onDrawFrame回調(diào)方法祸憋。當activity暫停時,會觸發(fā)glsurfaceview的onDrawFrame回調(diào)方法肖卧,這時會暫停渲染器的繪制操作蚯窥,并調(diào)用渲染器的onDrawFrame回調(diào)方法。當activity銷毀時塞帐,會觸發(fā)glsurfaceview的onSurfaceCreated回調(diào)方法拦赠,這時會銷毀渲染器對象,并調(diào)用渲染器的onSurfaceCreated回調(diào)方法葵姥。

在這個過程中荷鼠,需要注意以下幾點:

  • 在activity的onSaveInstanceState和onRestoreInstanceState方法中,可以保存或恢復一些重要的數(shù)據(jù)或狀態(tài)榔幸,比如使用一個bundle對象來存儲或獲取一些opengl es相關的對象或參數(shù)允乐。
  • 在glsurfaceview的onSurfaceChanged回調(diào)方法中,可以根據(jù)surface的寬高調(diào)整視口大小或投影方式削咆,比如使用glviewport或glfrustum等方法來設置視口或投影矩陣喳篇。
  • 在glsurfaceview的setRenderMode方法中,可以設置不同的渲染模式态辛,比如使用RENDERMODE_CONTINUOUSLY或RENDERMODE_WHEN_DIRTY來設置連續(xù)模式或按需模式麸澜。
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市奏黑,隨后出現(xiàn)的幾起案子炊邦,更是在濱河造成了極大的恐慌,老刑警劉巖熟史,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馁害,死亡現(xiàn)場離奇詭異,居然都是意外死亡蹂匹,警方通過查閱死者的電腦和手機碘菜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人忍啸,你說我怎么就攤上這事仰坦。” “怎么了计雌?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵悄晃,是天一觀的道長。 經(jīng)常有香客問我凿滤,道長妈橄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任翁脆,我火速辦了婚禮眷蚓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘反番。我一直安慰自己溪椎,他們只是感情好,可當我...
    茶點故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布恬口。 她就那樣靜靜地躺著校读,像睡著了一般。 火紅的嫁衣襯著肌膚如雪祖能。 梳的紋絲不亂的頭發(fā)上歉秫,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天,我揣著相機與錄音养铸,去河邊找鬼雁芙。 笑死,一個胖子當著我的面吹牛钞螟,可吹牛的內(nèi)容都是我干的兔甘。 我是一名探鬼主播,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼鳞滨,長吁一口氣:“原來是場噩夢啊……” “哼洞焙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拯啦,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤澡匪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后褒链,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唁情,經(jīng)...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年甫匹,在試婚紗的時候發(fā)現(xiàn)自己被綠了甸鸟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惦费。...
    茶點故事閱讀 40,928評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖抢韭,靈堂內(nèi)的尸體忽然破棺而出薪贫,到底是詐尸還是另有隱情,我是刑警寧澤篮绰,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站季惯,受9級特大地震影響吠各,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜勉抓,卻給世界環(huán)境...
    茶點故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一贾漏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧藕筋,春花似錦纵散、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至暇藏,卻和暖如春蜜笤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盐碱。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工把兔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓮顽。 一個月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓县好,卻偏偏與公主長得像,于是被迫代替她去往敵國和親暖混。 傳聞我的和親對象是個殘疾皇子缕贡,可洞房花燭夜當晚...
    茶點故事閱讀 45,937評論 2 361

推薦閱讀更多精彩內(nèi)容