安卓surfaceview的使用方式

news2025/1/8 21:22:14

1. 什么是surfaceview

surfaceview内部机制和外部层次结构

在安卓开发中,我们经常会遇到一些需要高性能、高帧率、高画质的应用场景,例如视频播放、游戏开发、相机预览等。这些场景中,我们需要直接操作图像数据,并且实时地显示到屏幕上。如果我们使用普通的view组件来实现这些功能,可能会遇到以下问题:

  • view组件是在主线程中进行绘制的,如果绘制过程耗时过长或者频繁刷新,可能会导致主线程阻塞,影响用户交互和界面响应。
  • view组件在绘制时没有使用双缓冲机制,也就是说每次绘制都是直接在屏幕上进行的,这可能会导致绘制过程中出现闪烁或者撕裂的现象。
  • view组件是基于view层次结构的,也就是说每个view都是一个矩形区域,如果我们想要实现一些不规则形状或者透明度变化的效果,可能会比较困难。

为了解决这些问题,安卓提供了一种特殊的view组件:surfaceview 。surfaceview拥有自己独立的surface,也就是一个可以在其上直接绘制内容的图形缓冲区。surfaceview的内容是透明的,可以嵌入到view层次结构中,并且可以和其他view进行重叠或者裁剪。surfaceview适用于需要频繁刷新或处理逻辑复杂的绘图场景,如视频播放、游戏等。

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

surfaceview和普通view

从图中可以看出,普通view是按照顺序依次绘制到屏幕上的,而surfaceview则是直接绘制到屏幕上的一个透明区域,并且可以和其他view进行重叠或者裁剪。

2. surfaceview和view的区别

从上面的介绍中,我们已经了解了surfaceview和普通view在显示效果上的区别。那么,在实现原理和使用方式上,它们又有什么不同呢?下面我们来对比一下它们的主要区别:

特点普通viewsurfaceview
更新方式主动更新,可以在任何时候调用invalidate方法来触发重绘,在onDraw方法中使用canvas进行绘制被动更新,不能直接控制重绘,需要通过一个子线程来进行页面的刷新,在子线程中直接操作surface进行绘制
刷新线程主线程刷新,可以保证界面的一致性和同步性,但是可能导致主线程阻塞或者掉帧子线程刷新,可以避免主线程阻塞,并且可以提高刷新频率和效率,但是需要注意线程间的通信和同步问题
缓冲机制无双缓冲机制,每次绘制都是直接在屏幕上进行,可以节省内存空间,但是可能导致闪烁或者撕裂的现象有双缓冲机制,每次绘制都是先在一个缓冲区中进行,然后再将缓冲区中的内容复制到屏幕上,可以避免闪烁或者撕裂的现象,并且可以提高绘制质量,但是需要消耗更多的内存空间
  • 更新方式:普通view适用于主动更新的情况,也就是说我们可以在任何时候调用view的invalidate方法来触发view的重绘,然后在onDraw方法中使用canvas进行绘制。而surfaceview主要用于被动更新的情况,也就是说我们不能直接控制surfaceview的重绘,而是需要通过一个子线程来进行页面的刷新,然后在子线程中直接操作surface进行绘制。
  • 刷新线程:普通view是在主线程里面进行刷新的,也就是说所有的绘制操作都是在主线程中完成的。这样的好处是可以保证界面的一致性和同步性,但是也有可能导致主线程阻塞或者掉帧。而surfaceview是通过一个子线程来进行页面的刷新的,也就是说所有的绘制操作都是在子线程中完成的。这样的好处是可以避免主线程阻塞,并且可以提高刷新频率和效率,但是也需要注意线程间的通信和同步问题。
  • 缓冲机制:普通view在绘图时没有使用双缓冲机制,也就是说每次绘制都是直接在屏幕上进行的。这样的好处是可以节省内存空间,但是也可能导致绘制过程中出现闪烁或者撕裂的现象。而surfaceview在底层实现机制中已经实现了双缓冲机制,也就是说每次绘制都是先在一个缓冲区中进行,然后再将缓冲区中的内容复制到屏幕上。这样的好处是可以避免闪烁或者撕裂的现象,并且可以提高绘制质量,但是也需要消耗更多的内存空间。

3. surfaceview的创建和使用

了解了surfaceview和普通view的区别之后,我们就可以开始创建和使用surfaceview了。创建自定义的surfaceview需要以下几个步骤:

  • 继承surfaceview:首先,我们需要创建一个自定义的类,继承自surfaceview,并实现两个接口:surfaceholder.callback和runnable。前者用于监听surface的状态变化,后者用于实现子线程的逻辑。
  • 初始化surfaceholder:其次,我们需要在构造方法中初始化surfaceholder对象,并注册surfaceholder的回调方法。surfaceholder是一个用于管理surface的类,它提供了一些方法来获取和操作surface。
  • 处理回调方法:然后,我们需要在回调方法中处理surface的创建、改变和销毁事件。当surface被创建时,我们需要启动子线程,并根据需要调整view的大小或位置;当surface被改变时,我们需要重新获取surface的宽高,并根据需要调整view的大小或位置;当surface被销毁时,我们需要停止子线程,并释放相关资源。
  • 实现run方法:接着,我们需要在run方法中实现子线程的绘图逻辑。我们可以使用一个循环来不断地刷新页面,并且根据不同的条件来控制循环的退出。
  • 获取canvas对象:最后,我们需要在draw方法中获取canvas对象,并通过lockcanvas和unlockcanvasandpost方法进行绘图操作。lockcanvas方法会返回一个canvas对象,我们可以使用它来对surface进行绘制;unlockcanvasandpost方法会将绘制好的内容显示到屏幕上,并且释放canvas对象。

下面给出一个简单的示例代码,实现了一个简单的画板功能:

//自定义类继承自SurfaceView,并实现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的回调方法
        mHolder.addCallback(this);
        //初始化画笔对象,设置颜色和宽度
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(10);
    }

    //当Surface被创建时,启动子线程,并根据需要调整View的大小或位置
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        //设置标志位为true,表示子线程可以开始运行
        mIsDrawing = true;
        //创建并启动子线程
        mThread = new Thread(this);
        mThread.start();
    }

    //当Surface被改变时,重新获取Surface的宽高,并根据需要调整View的大小或位置
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        //TODO: 根据需要调整View的大小或位置
    }

    //当Surface被销毁时,停止子线程,并释放相关资源
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        //设置标志位为false,表示子线程可以停止运行
        mIsDrawing = false;
        try {
            //等待子线程结束,并释放子线程对象
            mThread.join();
            mThread = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //实现run方法,实现子线程的绘图逻辑
    @Override
    public void run() {
        //使用一个循环来不断地刷新页面
        while (mIsDrawing) {
            //获取当前时间,用于计算绘制时间
            long start = System.currentTimeMillis();
            //调用draw方法进行绘制操作
            draw();
            //获取结束时间,用于计算绘制时间
            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上进行绘制操作,例如画线、画圆、画文字等

                //在本例中,我们简单地使用随机数生成一些坐标点,并用画笔连接它们,形成一条折线图

                //生成一个随机数对象
                Random random = new Random();
                //生成一个点的集合,用于存储坐标点
                List<Point> points = new ArrayList<>();
                //循环生成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方法将绘制好的内容显示到屏幕上,并释放canvas对象
                mHolder.unlockCanvasAndPost(mCanvas);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

下图展示了上述代码运行的效果:

简单的画板

从图中可以看出,我们在surface上绘制了一条随机的折线图,并且显示到了屏幕上。这只是一个简单的示例,我们可以根据自己的需求,实现更复杂的绘图逻辑和效果。

4. surfaceview和activity的生命周期

在使用surfaceview时,我们需要注意它和activity的生命周期之间的关系。因为surfaceview是嵌入到view层次结构中的,所以它会受到activity的生命周期的影响。但是,surfaceview也有自己的生命周期,它是由surfaceholder来管理的。因此,对于具有surfaceview的activity,存在两个单独但相互依赖的状态机:应用oncreate/onresume/onpause和已创建/更改/销毁的surface。

下图展示了这两个状态机之间的关系:

surfaceview和activity的状态机之间的关系

surfaceview和activity的生命周期

从图中可以看出,当activity被创建时,会触发surfaceview的创建;当activity被恢复时,会触发surfaceview的改变;当activity被暂停时,会触发surfaceview的销毁。因此,在这些事件中,我们需要做一些相应的处理,例如:

  • 启动/停止子线程:当surface被创建或者销毁时,我们需要启动或者停止子线程,并根据需要调整view的大小或位置。如果我们不及时地启动或者停止子线程,可能会导致内存泄漏或者空指针异常。
  • 保存/恢复状态:当activity被暂停时,我们需要从子线程中提取状态,并保存到bundle中;当activity被恢复时,我们需要从bundle中恢复状态,并传递给子线程。如果我们不及时地保存或者恢复状态,可能会导致数据丢失或者不一致。

5. surfaceview和glsurfaceview

在上面的内容中,我们介绍了如何使用surfaceview来实现一些高性能、高帧率、高画质的应用。但是,如果我们想要实现一些更加复杂和精美的3D图形效果,例如光照、阴影、纹理、动画等,那么我们就需要使用opengl es来进行渲染。opengl es是一种跨平台的图形库,它可以利用gpu加速来提高渲染效率。

为了方便我们使用opengl es进行渲染,安卓提供了一种专门用于渲染opengl es内容的surfaceview:glsurfaceview 。glsurfaceview是一种继承自surfaceview的组件,它在底层封装了egl上下文、线程间通信以及与activity生命周期交互等功能。使用glsurfaceview时,我们无需自己创建和管理子线程,只需实现glsurfaceview.renderer接口,并设置给glsurfaceview对象即可。

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

glsurfaceview和普通surfaceview

从图中可以看出,glsurfaceview和普通surfaceview都是直接绘制到屏幕上的一个透明区域,但是glsurfaceview可以使用opengl es来绘制一些更加复杂和精美的3D图形效果。

6. glsurfaceview的创建和使用

了解了glsurfaceview和普通surfaceview的区别之后,我们就可以开始创建和使用glsurfaceview了。创建自定义的glsurfaceview需要以下几个步骤:

  • 继承glsurfaceview:首先,我们需要创建一个自定义的类,继承自glsurfaceview,并在构造方法中初始化相关对象。
  • 设置渲染器:其次,我们需要实现glsurfaceview.renderer接口,并设置给glsurfaceview对象。渲染器是一个用于绘制opengl es内容的类,它提供了三个方法:onSurfaceCreated、onSurfaceChanged和onDrawFrame。
  • 设置渲染模式:然后,我们需要设置glsurfaceview的渲染模式,有两种可选:RENDERMODE_CONTINUOUSLY和RENDERMODE_WHEN_DIRTY。前者表示持续地刷新页面,后者表示只有在调用requestRender方法时才刷新页面。
  • 获取opengl es对象:最后,我们需要在渲染器的方法中获取opengl es对象,并使用它来进行绘制操作。opengl es对象是一个用于操作图形数据的类,它提供了一系列的方法来创建、加载、绘制、变换、释放等图形资源。

下面给出一个简单的示例代码,实现了一个简单的3D立方体效果:

//自定义类继承自GLSurfaceView,并在构造方法中初始化相关对象
public class MyGLSurfaceView extends GLSurfaceView {

    //声明渲染器对象
    private MyRenderer mRenderer;

    //构造方法,初始化相关对象
    public MyGLSurfaceView(Context context) {
        super(context);
        //设置opengl es版本为2.0
        setEGLContextClientVersion(2);
        //创建并设置渲染器对象
        mRenderer = new MyRenderer();
        setRenderer(mRenderer);
        //设置渲染模式为持续刷新
        setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }

    //自定义类实现GLSurfaceView.Renderer接口,并实现三个方法
    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;" +
                "}";

        //声明顶点坐标数组
        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
        };

        //声明顶点索引数组
        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
        };

        //声明颜色数组
        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;
        //声明程序对象
        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被创建时,初始化opengl es对象,并加载和编译着色器,创建和绑定图形数据,设置相机位置和投影方式等操作
        @Override
        public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
            //获取opengl es对象,用于后续的绘制操作
            gl = (GLES20) gl10;

            //设置背景颜色为黑色,用于清除屏幕时使用
            gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

            //加载和编译顶点着色器,返回一个句柄,用于后续的链接操作
            vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
                    vertexShaderCode);

            //加载和编译片元着色器,返回一个句柄,用于后续的链接操作
            fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);

            //创建一个空的程序对象,返回一个句柄,用于后续的链接操作
            program = GLES20.glCreateProgram();

            //将顶点着色器和片元着色器附加到程序对象上
            GLES20.glAttachShader(program, vertexShader);
            GLES20.glAttachShader(program, fragmentShader);

            //链接程序对象,生成最终的可执行程序
            GLES20.glLinkProgram(program);

            //使用程序对象,激活相关的属性和统一变量
            GLES20.glUseProgram(program);

            //获取顶点位置属性的句柄,用于后续的绑定操作
            positionHandle = GLES20.glGetAttribLocation(program, "vPosition");

            //获取颜色属性的句柄,用于后续的绑定操作
            colorHandle = GLES20.glGetUniformLocation(program, "vColor");

            //获取投影矩阵属性的句柄,用于后续的绑定操作
            mvpMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");

            //将顶点坐标数组转换为字节缓冲对象,用于后续的传输操作
            ByteBuffer bb = ByteBuffer.allocateDirect(
                    vertexCoords.length * 4);
            bb.order(ByteOrder.nativeOrder());
            vertexBuffer = bb.asFloatBuffer();
            vertexBuffer.put(vertexCoords);
            vertexBuffer.position(0);

            //将顶点索引数组转换为字节缓冲对象,用于后续的传输操作
            ByteBuffer dlb = ByteBuffer.allocateDirect(
                    drawOrder.length * 2);
            dlb.order(ByteOrder.nativeOrder());
            drawListBuffer = dlb.asShortBuffer();
            drawListBuffer.put(drawOrder);
            drawListBuffer.position(0);

            //将颜色数组转换为字节缓冲对象,用于后续的传输操作
            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被改变时,调整视口大小,并设置投影方式,生成投影矩阵
        @Override
        public void onSurfaceChanged(GL10 gl10, int width, int height) {
            //设置视口大小为Surface的大小
            GLES20.glViewport(0, 0, width, height);

            //设置投影方式为透视投影,并根据视口宽高比计算投影矩阵
            float ratio = (float) width / height;
            Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);

        }

        //当Surface被绘制时,清除屏幕,并旋转模型矩阵,生成模型视图投影矩阵,并传输和绘制图形数据
        @Override
        public void onDrawFrame(GL10 gl10) {
            //清除屏幕颜色缓冲区
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

            //设置模型矩阵为单位矩阵,并根据系统时间旋转模型矩阵
            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);

            //将模型视图投影矩阵传输到顶点着色器中,并激活该属性
            GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mvpMatrix, 0);

            //将顶点坐标数据传输到顶点着色器中,并激活该属性
            GLES20.glVertexAttribPointer(positionHandle, 3,
                    GLES20.GL_FLOAT, false,
                    0, vertexBuffer);
            GLES20.glEnableVertexAttribArray(positionHandle);

            //使用循环为每个面设置不同的颜色,并绘制三角形
            for (int i = 0; i < 6; i++) {
                //将颜色数据传输到片元着色器中,并激活该属性
                colorBuffer.position(i * 4);
                GLES20.glUniform4fv(colorHandle, 1, colorBuffer);
                //绘制三角形,使用顶点索引数组来确定顶点的顺序
                drawListBuffer.position(i * 6);
                GLES20.glDrawElements(
                        GLES20.GL_TRIANGLES, 6,
                        GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
            }

        }

        //定义一个加载和编译着色器的方法,接收一个着色器类型和一个着色器代码,返回一个着色器句柄
        public int loadShader(int type, String shaderCode) {

            //创建一个空的着色器对象,返回一个句柄,用于后续的编译操作
            int shader = GLES20.glCreateShader(type);

            //将着色器代码传输到着色器对象中
            GLES20.glShaderSource(shader, shaderCode);

            //编译着色器对象
            GLES20.glCompileShader(shader);

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

glsurfaceview的使用方式

什么是glsurfaceview

glsurfaceview是一种专门用于渲染opengl es内容的surfaceview。opengl es是一种用于嵌入式设备上的3D图形渲染API。glsurfaceview类提供了用于管理egl上下文、在线程间通信以及与activity生命周期交互的辅助程序类。使用glsurfaceview时,无需自己创建和管理子线程,只需实现glsurfaceview.renderer接口,并设置给glsurfaceview对象即可。

glsurfaceview和surfaceview的区别

glsurfaceview和surfaceview都是继承自surfaceview的类,都可以在子线程中直接操作surface进行绘制。但是glsurfaceview相比surfaceview有以下的优势:

  • glsurfaceview可以自动创建和管理egl上下文,无需自己处理egl的初始化、销毁、切换等操作。
  • glsurfaceview可以自动创建和管理子线程,无需自己处理线程的启动、停止、同步等操作。
  • glsurfaceview可以自动处理与activity生命周期的交互,无需自己处理activity的暂停、恢复、保存状态等操作。
  • glsurfaceview可以提供多种渲染模式,可以根据需要调整渲染频率,避免过度绘制或掉帧。

glsurfaceview的创建和使用

创建自定义的glsurfaceview继承glsurfaceview,并在构造方法中设置opengl es版本、渲染器对象和渲染模式。创建自定义的渲染器实现glsurfaceview.renderer接口,并在回调方法中进行初始化、视口设置和绘图操作。

以下是一个简单的示例代码:

// 自定义的glsurfaceview类
public class MyGLSurfaceView extends GLSurfaceView {

    // 构造方法
    public MyGLSurfaceView(Context context) {
        super(context);
        // 设置opengl es版本为2.0
        setEGLContextClientVersion(2);
        // 设置渲染器对象
        setRenderer(new MyRenderer());
        // 设置渲染模式为连续模式
        setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }

    // 自定义的渲染器类
    private class MyRenderer implements GLSurfaceView.Renderer {

        // 渲染器创建时的回调方法
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            // 在这里进行一些初始化操作,比如设置清屏颜色为黑色
            GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        }

        // 渲染器改变时的回调方法
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            // 在这里进行一些视口设置操作,比如设置视口大小为surface的大小
            GLES20.glViewport(0, 0, width, height);
        }

        // 渲染器绘制时的回调方法
        @Override
        public void onDrawFrame(GL10 gl) {
            // 在这里进行一些绘图操作,比如清屏
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        }
    }
}

glsurfaceview和activity的生命周期

当使用glsurfaceview时,无需自己处理与activity生命周期的交互,glsurfaceview会自动根据activity的状态来暂停或恢复渲染器。但是如果需要保存或恢复一些重要的数据或状态,可以在activity的onSaveInstanceState和onRestoreInstanceState方法中进行操作。

以下是一个示意图,展示了glsurfaceview和activity的生命周期之间的关系:

glsurfaceview和activity的生命周期

从图中可以看出,当activity创建时,会触发glsurfaceview的onSurfaceCreated回调方法,这时会创建渲染器对象,并调用渲染器的onSurfaceCreated回调方法。当activity恢复时,会触发glsurfaceview的onDrawFrame回调方法,这时会恢复渲染器的绘制操作,并调用渲染器的onDrawFrame回调方法。当activity暂停时,会触发glsurfaceview的onDrawFrame回调方法,这时会暂停渲染器的绘制操作,并调用渲染器的onDrawFrame回调方法。当activity销毁时,会触发glsurfaceview的onSurfaceCreated回调方法,这时会销毁渲染器对象,并调用渲染器的onSurfaceCreated回调方法。

在这个过程中,需要注意以下几点:

  • 在activity的onSaveInstanceState和onRestoreInstanceState方法中,可以保存或恢复一些重要的数据或状态,比如使用一个bundle对象来存储或获取一些opengl es相关的对象或参数。
  • 在glsurfaceview的onSurfaceChanged回调方法中,可以根据surface的宽高调整视口大小或投影方式,比如使用glviewport或glfrustum等方法来设置视口或投影矩阵。
  • 在glsurfaceview的setRenderMode方法中,可以设置不同的渲染模式,比如使用RENDERMODE_CONTINUOUSLY或RENDERMODE_WHEN_DIRTY来设置连续模式或按需模式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1653164.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

后端常用技能:解决java项目前后端传输数据中文出现乱码、问号问题

0. 问题背景 最近做一个解析数据的小工具&#xff0c;本地运行时都正常&#xff0c;发布到服务器上后在导出文件数据时发现中文全部变成了问号&#xff0c;特此记录下问题解决的思路和过程 1. 环境 java 1.8 springboot 2.6.13 额外引入了fastjson&#xff0c;commons-csv等…

##07 从线性回归到神经网络:PyTorch实战解析

文章目录 前言线性回归基础理论背景实现步骤神经网络介绍理论背景实现步骤从线性回归到神经网络结论前言 在深度学习的浩瀚宇宙中,线性回归和神经网络是两个基本但极其重要的概念。线性回归模型是统计学和机器学习的基石之一,而神经网络则是深度学习技术的核心。本文旨在通过…

【日志革新】在ThinkPHP5中实现高效TraceId集成,打造可靠的日志追踪系统

问题背景 最近接手了一个骨灰级的项目&#xff0c;然而在项目中遇到了一个普遍的挑战&#xff1a;由于公司采用 ELK&#xff08;Elasticsearch、Logstash、Kibana&#xff09;作为日志收集和分析工具&#xff0c;追踪生产问题成为了一大难题。尽管 ELK 提供了强大的日志分析功…

在时间同步应用上节省大量时间!德思特GNSS模拟器是怎么做到的?

​ 作者介绍 德思特Safran GNSS模拟器是一款综合解决方案&#xff0c;专为精确的PNT&#xff08;位置、导航和时间&#xff09;仿真与测试设计。它超越了传统GNSS定位导航仿真&#xff0c;也能提供极高的授时精度。 这款模拟器对于评估和提升GNSS接收机及同步系统的整体性能至…

前端JS必用工具【js-tool-big-box】,验证是否是Unicode字符,获取一个字符串的字节长度,以及新增发送JSONP跨域请求的方法

js-tool-big-box&#xff0c;目前已经收集到了用户需求&#xff0c;希望可以添加一些公用方法&#xff0c;我觉得这很好&#xff0c;我们一起把这个前端通用工具做大一些&#xff0c;帮助更多的小伙伴少些util代码&#xff0c;更多的关注于自己的业务开发&#xff0c;真是不错。…

OpenCV与AI深度学习 | 如何使用YOLOv9检测图片和视频中的目标

本文来源公众号“OpenCV与AI深度学习”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;如何使用YOLOv9检测图片和视频中的目标 1 介绍 在之前的文章中&#xff0c;我们探索了使用 YOLOv8 进行对象检测。现在&#xff0c;我们很高兴…

【仪酷LabVIEW AI工具包案例】使用LabVIEW AI工具包+YOLOv5结合Dobot机械臂实现智能垃圾分类

‍‍&#x1f3e1;博客主页&#xff1a; virobotics(仪酷智能)&#xff1a;LabVIEW深度学习、人工智能博主 &#x1f384;所属专栏&#xff1a;『仪酷LabVIEW AI工具包案例』 &#x1f4d1;上期文章&#xff1a;『【YOLOv9】实战二&#xff1a;手把手教你使用TensorRT实现YOLOv…

Spring - 7 ( 13000 字 Spring 入门级教程 )

一&#xff1a;Spring Boot 日志 1.1 日志概述 日志对我们来说并不陌生&#xff0c;我们可以通过打印日志来发现和定位问题, 或者根据日志来分析程序的运行过程&#xff0c;但随着项目的复杂度提升, 我们对日志的打印也有了更高的需求, 而不仅仅是定位排查问题 比如有时需要…

关于 c++ 中字符串 string 及 常量字符串的换行与使用

&#xff08;1&#xff09;例如 cout << " ddddddddddddddddddd" 。当输出字符太长&#xff0c;就需要换行。疑问是如何写代码&#xff0c;才可以保证源代码中的字符串换行被正确编译呢&#xff1f;测试一下&#xff0c;如下图可见&#xff0c;如此换行&#x…

STM32:GPIO输入输出

文章目录 1、GPIO介绍1.1 GPIO的基本结构1.1 GPIO的位结构 2、 GPIO工作模式3、GPIO标准外设库接口函数3.1 RCC接口函数3.2 GPIO接口函数3.2.1 GPIO的读取函数3.2.1 GPIO的写入函数 4、GPIO的初始化 1、GPIO介绍 GPIO&#xff08;General Purpose Input Output&#xff09;通用…

【MQTT】服务端、客户端工具使用记录

目录 一、服务端 1.1 下载 1.2 相关命令 &#xff08;1&#xff09;启动 &#xff08;2&#xff09;关闭 &#xff08;3&#xff09;修改用户名和密码 1.3 后台管理 &#xff08;1&#xff09;MQTT配置 &#xff08;2&#xff09;集群概览 &#xff08;3&#xff09;…

场外期权个股怎么对冲?

今天期权懂带你了解场外期权个股怎么对冲&#xff1f;场外个股期权是一种在非交易所市场进行的期权交易&#xff0c;它允许投资者针对特定的股票获得未来买入或卖出的权利。 场外期权个股怎么对冲&#xff1f; 持有相反方向的期权&#xff1a;这是最直接的对冲方法&#xff0c…

今晚 19:00 | 从这两个问题入手,带你了解数据要素相关税务问题

五一假期已经结束&#xff0c;返工后当然是继续劳动啦~数据要素系列直播《星光对话》第三期也将在今晚19:00&#xff0c;继续跟大家见面。 本期直播&#xff0c;依然由 星光数智咨询总监 刘靖 主讲&#xff0c;带来&#xff1a;《数据要素相关税务问题解读》。 主要围绕两个问题…

怎么快速分享视频文件?用二维码看视频的方法

怎样不通过传输下载分享视频内容呢&#xff1f;以前分享视频内容&#xff0c;大多会通过微信、QQ、邮箱、网盘等形式来传递。但是这种方式需要下载后才可以观看&#xff0c;不仅占用手机内存&#xff0c;而且效率也比较低&#xff0c;所以现在很多人会采用视频生成二维码的方式…

为 Flutter 应用设置主题:ThemeData 和 ColorScheme 指南

在媒体和其他来源中有许多关于这个主题的文章&#xff0c;那么这篇文章的必要性是什么&#xff1f; 在本文中&#xff0c;我计划仅关注 ThemeData 小部件的关键点以及我的开发经验中最常用的参数&#xff0c;并且您将获得有关每个参数如何对您的应用程序执行操作的简要说明。 …

LeetCode70:爬楼梯

题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 解题思想 1.确定dp数组以及下标的含义 dp[i]&#xff1a; 爬到第i层楼梯&#xff0c;有dp[i]种方法 2.确定递推公式 从dp[i]的定义可以…

旋转测径仪的常见故障和排除方法

关键字: 旋转测径仪,测径仪常见故障,测径仪故障排除方法,测径仪维护,测径仪较零 点击 “开始测量”按钮时提示“初始化失败&#xff01;”无法进行测量。 ◆ 检查控制柜面板“工作”指示灯&#xff08;绿&#xff09;是否点亮&#xff1b; ◆ 最小化软件窗口&#xff0c;查看…

解密某游戏的数据加密

前言 最近有个兄弟通过我的视频号加我&#xff0c;咨询能否将这个dubo游戏游戏开始前就将数据拿到从而进行押注&#xff0c;于是通过抓包工具测试了下&#xff0c;发现数据有时候是明文&#xff0c;有时候确实密文&#xff0c;大致看了下有这几种加密&#xff1a;Md5aes、Md5&a…

用Pycharm对图片中表格进行文字识别,并导出到xlsx文件中

需要使用到百度API对图片文字进行识别 在百度智能云官网中注册一个账号百度智能云-登录https://login.bce.baidu.com/ 之后在管理中心里创建应用 创建完成后会得到&#xff1a; 记下一下AppID&API Key&Secret Key这三个值&#xff0c;调用接口时使用。 示例图片&…

字符编码(十六进制)

题目描述 假设一个简易的变长编码规则XUTF:每个字符有一个唯一编号值 unicodeVal(如汉字“华”的编号十进制值是21326),使用1~6个字节进行编码,编码规则如下: 编码格式: 编号值范围编码后字节长度n二进制格式(x 表示有效位,其它为固定位)[0, 2^7)1字节1xxxxxxx[2^7,…