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

Jetpack Compose中AR屏幕截图黑屏问题求助

解决Jetpack Compose中AR屏幕截图黑屏问题

问题分析

你当前遇到的核心问题是:AR视图(通常是SurfaceView或TextureView)的渲染内容在GPU层面,而非普通View的绘制缓存中。直接调用view.draw(canvas)无法捕获这类视图的实时画面,导致截图黑屏;虽然通过回调获取ARView去掉了相机按钮,但截图方式不对的问题仍未解决。

解决方案

根据AR视图的实现类型,提供两种可行方案:

方案1:基于ARCore官方API截图(若使用ARCore)

ARCore的ARSession自带截图能力,可直接捕获包含相机画面和AR物体的帧数据,无需手动绘制View。

步骤1:提取保存逻辑为独立函数

将原有的保存代码从Composable中剥离,避免在UI函数中执行IO操作:

fun saveBitmapToGallery(bitmap: Bitmap, context: Context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val resolver = context.contentResolver
        val contentValues = ContentValues().apply {
            put(MediaStore.Images.Media.DISPLAY_NAME, "AR_Screenshot_${System.currentTimeMillis()}")
            put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
            put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
        }
        val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
        uri?.let { imageUri ->
            resolver.openOutputStream(imageUri)?.use { outputStream ->
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
            }
            Toast.makeText(context, "截图已保存", Toast.LENGTH_SHORT).show()
        } ?: run {
            Toast.makeText(context, "保存失败", Toast.LENGTH_SHORT).show()
        }
    } else {
        val file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
        val filename = "AR_Screenshot_${System.currentTimeMillis()}.jpg"
        val fileUri = File(file, filename).absolutePath
        try {
            if (!file.exists()) file.mkdirs()
            val outputStream = FileOutputStream(fileUri)
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
            outputStream.close()
            MediaScannerConnection.scanFile(context, arrayOf(fileUri), null, null)
            Toast.makeText(context, "截图已保存", Toast.LENGTH_SHORT).show()
        } catch (e: Exception) {
            Toast.makeText(context, "保存失败", Toast.LENGTH_SHORT).show()
            e.printStackTrace()
        }
    }
}

步骤2:实现ARCore截图函数

fun captureARScreenshot(session: ARSession, context: Context) {
    val arImage = session.captureScreenShot()
    arImage?.let {
        val bitmap = Bitmap.createBitmap(it.width, it.height, Bitmap.Config.ARGB_8888)
        it.copyToBitmap(bitmap)
        saveBitmapToGallery(bitmap, context)
        it.close()
    } ?: run {
        Toast.makeText(context, "截图失败", Toast.LENGTH_SHORT).show()
    }
}

步骤3:调整Composable代码

@Composable
fun ARScreen(onSessionReady: (ARSession) -> Unit) {
    AndroidView(
        factory = { context ->
            val arView = ArView(context)
            // 监听Session初始化完成
            arView.addSessionListener(object : SessionListener {
                override fun onSessionCreated(session: ARSession) {
                    onSessionReady(session)
                }
                // 其他回调可按需实现
            })
            arView
        }
    )
}

@Composable
fun Activity_3() {
    var captureScreenshot by remember { mutableStateOf(false) }
    var arSession by remember { mutableStateOf<ARSession?>(null) }
    val context = LocalContext.current

    // 用LaunchedEffect处理截图触发,避免在Composable同步块执行耗时操作
    LaunchedEffect(captureScreenshot) {
        if (captureScreenshot) {
            arSession?.let { captureARScreenshot(it, context) }
            captureScreenshot = false
        }
    }

    Surface(color = MaterialTheme.colors.background) {
        Box(modifier = Modifier.fillMaxSize()) {
            ARScreen(onSessionReady = { session ->
                arSession = session
            })
            CameraButton {
                captureScreenshot = true
            }
        }
    }
}

方案2:使用PixelCopy捕获SurfaceView/TextureView(自定义AR视图)

如果你的AR视图是SurfaceView或TextureView,用Android官方的PixelCopy API捕获GPU渲染画面:

实现截图函数

fun captureARSurfaceView(view: View, context: Context) {
    val surface = (view as? SurfaceView)?.holder?.surface ?: return
    val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
    
    PixelCopy.request(
        surface,
        bitmap,
        { result ->
            if (result == PixelCopy.SUCCESS) {
                saveBitmapToGallery(bitmap, context)
            } else {
                Toast.makeText(context, "截图失败", Toast.LENGTH_SHORT).show()
            }
        },
        Handler(Looper.getMainLooper())
    )
}

调整Activity_3触发逻辑

@Composable
fun Activity_3() {
    var captureScreenshot by remember { mutableStateOf(false) }
    var arView by remember { mutableStateOf<View?>(null) }
    val context = LocalContext.current

    LaunchedEffect(captureScreenshot) {
        if (captureScreenshot) {
            arView?.let { captureARSurfaceView(it, context) }
            captureScreenshot = false
        }
    }

    Surface(color = MaterialTheme.colors.background) {
        Box(modifier = Modifier.fillMaxSize()) {
            ARScreen(onViewReady = { view ->
                arView = view
            })
            CameraButton {
                captureScreenshot = true
            }
        }
    }
}

关键注意事项

  • 禁止在Composable函数中直接调用view.draw(canvas)捕获AR视图,这类视图的内容不在View绘制树中。
  • 确保ARSession/AR视图完全初始化后再触发截图,避免空指针或未渲染完成的情况。
  • 截图、保存等耗时操作需放在非Composable函数中,用LaunchedEffect或协程处理,避免阻塞UI线程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 05:35:20