You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Jetpack Compose中渲染OpenGL且不遮挡其他UI元素

解决方案:Jetpack Compose 与 OpenGL 视图的层级及布局问题

一、通过 Compose 调整 OpenGL 窗口大小

当前问题核心是CustomView使用Modifier.fillMaxSize()占满了整个Column的垂直空间,导致后续的Text、Button被挤出可视区域,同时GLSurfaceView的独立渲染层会覆盖其他UI。

修改方案

  1. 让CustomView支持接收外部Modifier,灵活控制尺寸
  2. 在Column中为OpenGL视图分配合理的布局空间

示例代码:

@Composable
fun CustomView(modifier: Modifier = Modifier) {
    AndroidView(
        modifier = modifier,
        factory = { context ->
            MyGLSurfaceView(context)
        }
    )
}

@Composable
fun ContentExample() {
    Column(Modifier.fillMaxSize()) {
        // 让OpenGL视图占满Column剩余空间,下方元素正常显示
        CustomView(modifier = Modifier.weight(1f))
        Text("Look at this CustomView!")
        Button(onClick = { }) {
            Text("Test button")
        }
    }
}

也可以直接给CustomView设置固定尺寸:

@Composable
fun ContentExample() {
    Column(Modifier.fillMaxSize()) {
        CustomView(modifier = Modifier
            .fillMaxWidth()
            .height(200.dp)) // 固定高度
        Text("Look at this CustomView!")
        Button(onClick = { }) {
            Text("Test button")
        }
    }
}

二、让 Compose UI 元素显示在 OpenGL 视图上方

GLSurfaceView继承自SurfaceView,其渲染Surface是独立于Android View层级的系统级图层,默认会覆盖所有普通View(包括Compose渲染的UI)。推荐以下两种解决方法:

方法1:使用 TextureView 替代 SurfaceView(推荐)

TextureView属于普通View层级,不会产生独立图层的覆盖问题,需要自定义支持OpenGL渲染的TextureView:

class MyGLTextureView(context: Context) : TextureView(context), TextureView.SurfaceTextureListener {
    private var renderer: MyGLRenderer? = null
    private var glSurface: EGLSurface? = null
    private var glContext: EGLContext? = null

    init {
        surfaceTextureListener = this
    }

    override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
        // 初始化EGL环境
        val egl = EGLContext.getEGL()
        val display = egl.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
        egl.eglInitialize(display, intArrayOf(0, 0), 0, intArrayOf(0, 0), 0)

        val configAttribs = intArrayOf(
            EGL14.EGL_RED_SIZE, 8,
            EGL14.EGL_GREEN_SIZE, 8,
            EGL14.EGL_BLUE_SIZE, 8,
            EGL14.EGL_ALPHA_SIZE, 8,
            EGL14.EGL_DEPTH_SIZE, 16,
            EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
            EGL14.EGL_NONE
        )
        val configs = arrayOfNulls<EGLConfig>(1)
        val numConfigs = IntArray(1)
        egl.eglChooseConfig(display, configAttribs, 0, configs, 0, 1, numConfigs, 0)
        val config = configs[0]!!

        val contextAttribs = intArrayOf(
            EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
            EGL14.EGL_NONE
        )
        glContext = egl.eglCreateContext(display, config, EGL14.EGL_NO_CONTEXT, contextAttribs, 0)

        glSurface = egl.eglCreateWindowSurface(display, config, surface, null, 0)
        egl.eglMakeCurrent(display, glSurface, glSurface, glContext)

        renderer = MyGLRenderer()
        renderer?.onSurfaceCreated(null, config)
        renderer?.onSurfaceChanged(null, width, height)

        // 启动渲染线程维持画面更新
        Thread {
            while (true) {
                egl.eglMakeCurrent(display, glSurface, glSurface, glContext)
                renderer?.onDrawFrame(null)
                egl.eglSwapBuffers(display, glSurface)
                Thread.sleep(16) // 约60fps
            }
        }.start()
    }

    override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
        renderer?.onSurfaceChanged(null, width, height)
    }

    override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
        val egl = EGLContext.getEGL()
        val display = egl.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
        egl.eglMakeCurrent(display, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT)
        glSurface?.let { egl.eglDestroySurface(display, it) }
        glContext?.let { egl.eglDestroyContext(display, it) }
        egl.eglTerminate(display)
        return true
    }

    override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
}

之后修改CustomView使用这个自定义TextureView,并用Box实现层叠布局:

@Composable
fun CustomView(modifier: Modifier = Modifier) {
    AndroidView(
        modifier = modifier.fillMaxSize(),
        factory = { context ->
            MyGLTextureView(context)
        }
    )
}

@Composable
fun ContentExample() {
    Box(Modifier.fillMaxSize()) {
        // OpenGL视图在底层
        CustomView()
        // Compose元素在顶层,可自由调整位置
        Column(
            modifier = Modifier
                .align(Alignment.BottomCenter)
                .padding(16.dp)
        ) {
            Text(
                "Look at this CustomView!",
                color = Color.White
            )
            Button(onClick = { }) {
                Text("Test button")
            }
        }
    }
}

方法2:调整 GLSurfaceView 的 Z 轴(不推荐)

SurfaceView的层级由系统控制,兼容性较差,可尝试调用setZOrderMediaOverlay(true)或setZOrderOnTop(false)调整,但仅部分场景有效,不建议作为通用方案。

内容的提问来源于stack exchange,提问作者strbry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 11:55:05