1. 什么是surfaceview
在安卓開發(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在屏幕上的顯示效果:
從圖中可以看出,普通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)機之間的關系:
從圖中可以看出痒给,當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都是直接繪制到屏幕上的一個透明區(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的生命周期之間的關系:
從圖中可以看出,當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ù)模式或按需模式麸澜。