如何在Jetpack Compose中渲染OpenGL且不遮挡其他UI元素
解决方案:Jetpack Compose 与 OpenGL 视图的层级及布局问题
一、通过 Compose 调整 OpenGL 窗口大小
当前问题核心是CustomView使用Modifier.fillMaxSize()占满了整个Column的垂直空间,导致后续的Text、Button被挤出可视区域,同时GLSurfaceView的独立渲染层会覆盖其他UI。
修改方案
- 让
CustomView支持接收外部Modifier,灵活控制尺寸 - 在
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
相关产品推荐
相关产品推荐

