0Pricing
Android Academy · 课时

显示摄像头预览

连接实时预览

显示摄像头预览 是 CoddyKit 上的免费 Android Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Android Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Android Academy 课程共包含 4 节课。

目标:实时预览

几乎所有相机应用首先需要的功能都是实时预览——实时显示相机所看到的画面。在本课中,您将构建一个全屏显示相机画面的 Compose 屏幕。

您将组合三个部分:PreviewView、Preview 用例和已绑定的 ProcessCameraProvider。

请求相机权限

在显示预览之前,您需要获得用户的权限。在 Compose 中,Accompanist(或官方权限 API)配合 rememberPermissionState 可以轻松完成这项工作。

只有在权限状态为 granted 后,才启动相机。

@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun CameraScreen() {
    val permission = rememberPermissionState(Manifest.permission.CAMERA)

    LaunchedEffect(Unit) { permission.launchPermissionRequest() }

    if (permission.status.isGranted) {
        CameraPreview()
    } else {
        Text("Camera permission is required.")
    }
}

在 Compose 中嵌入 PreviewView

PreviewView 是传统的 Android View,因此您需要使用 AndroidView 将它放入 Compose 中。factory lambda 会创建一次视图,并为您提供一个可以配置的引用。

请保存创建的视图,以便将相机表面连接到它。

@Composable
fun CameraPreview(modifier: Modifier = Modifier) {
    AndroidView(
        modifier = modifier.fillMaxSize(),
        factory = { context ->
            PreviewView(context).apply {
                scaleType = PreviewView.ScaleType.FILL_CENTER
            }
        }
    )
}

获取 LifecycleOwner

要绑定相机,您需要一个 LifecycleOwner。在 Compose 中,您可以使用 LocalLifecycleOwner.current 读取当前的生命周期所有者,并使用 LocalContext.current 获取上下文。

设置相机 provider 和绑定用例时,这两者都是必需的。

val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current

构建预览用例

Preview 用例会将帧流式传输到一个表面。构建完成后,您可以调用 setSurfaceProvider,将视图的表面 provider 传给它,从而把预览连接到 PreviewView。

正是这一步连接让相机画面真正显示在屏幕上。

val preview = Preview.Builder().build()
preview.setSurfaceProvider(previewView.surfaceProvider)

在协程中进行绑定

获取 provider 是异步操作,因此应在协程中完成。使用与视图关联的 LaunchedEffect,让代码只运行一次。在其中等待 provider,解除之前的所有绑定,然后绑定预览。

LaunchedEffect(previewView) {
    val cameraProvider = ProcessCameraProvider.getInstance(context).await()
    val preview = Preview.Builder().build().also {
        it.setSurfaceProvider(previewView.surfaceProvider)
    }
    cameraProvider.unbindAll()
    cameraProvider.bindToLifecycle(
        lifecycleOwner,
        CameraSelector.DEFAULT_BACK_CAMERA,
        preview
    )
}

await() 扩展函数

ProcessCameraProvider.getInstance() 会返回一个 ListenableFuture。kotlinx-coroutines-guava 提供的 await() 扩展函数会一直挂起,直到结果就绪,从而将回调代码转换为整洁的顺序代码。

请添加依赖项 org.jetbrains.kotlinx:kotlinx-coroutines-guava 来使用它。

import kotlinx.coroutines.guava.await

// Without await you'd use a listener:
// future.addListener({ val provider = future.get() }, executor)
// await() makes it a single suspend call instead.

整合起来

下面是完整的预览可组合项。请注意其流程:在 factory 中创建 PreviewView,获取它的引用,然后在 LaunchedEffect 中进行绑定。

@Composable
fun CameraPreview(modifier: Modifier = Modifier) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    val previewView = remember { PreviewView(context) }

    LaunchedEffect(previewView) {
        val provider = ProcessCameraProvider.getInstance(context).await()
        val preview = Preview.Builder().build().also {
            it.setSurfaceProvider(previewView.surfaceProvider)
        }
        provider.unbindAll()
        provider.bindToLifecycle(
            lifecycleOwner,
            CameraSelector.DEFAULT_BACK_CAMERA,
            preview
        )
    }

    AndroidView(
        factory = { previewView },
        modifier = modifier.fillMaxSize()
    )
}

缩放类型

相机传感器的宽高比很少与屏幕完全匹配。PreviewView.ScaleType 用于控制画面如何适应视图:

  • FILL_CENTER——填满视图并裁剪边缘(最常用)。
  • FIT_CENTER——显示完整画面,并在两侧或上下留下黑边。

要实现沉浸式全屏相机,请选择 FILL_CENTER。

previewView.scaleType = PreviewView.ScaleType.FILL_CENTER
// or
previewView.scaleType = PreviewView.ScaleType.FIT_CENTER

切换相机

要在前置和后置相机之间切换,请将选择器存储在状态中,并在它发生变化时重新绑定。由于 LaunchedEffect 会在键发生变化时重新运行,只需将选择器作为键传入即可。

var selector by remember { mutableStateOf(CameraSelector.DEFAULT_BACK_CAMERA) }

LaunchedEffect(selector) {
    val provider = ProcessCameraProvider.getInstance(context).await()
    provider.unbindAll()
    provider.bindToLifecycle(lifecycleOwner, selector, preview)
}

// Flip button toggles 'selector' between front and back.

常见问题

以下两个错误会导致预览画面变黑:

  • 忘记调用 setSurfaceProvider——帧没有传输目标。
  • 重新绑定前没有调用 unbindAll()——如果同一个用例被绑定两次,CameraX 会抛出错误。

还请记住:只有在权限实际获得后,预览才会启动。

快速检查

检查您对连接预览过程的理解。

回顾

您已经在 Compose 中构建了实时相机预览:

  • 启动相机前请求 CAMERA 权限。
  • 使用 AndroidView 承载 PreviewView。
  • 在协程中等待 ProcessCameraProvider。
  • 构建 Preview,调用 setSurfaceProvider 和 unbindAll(),然后调用 bindToLifecycle。

接下来,您将添加 ImageCapture 用例并拍摄真实照片。

常见问题解答

「显示摄像头预览」课时是免费的吗?

是的 — 「显示摄像头预览」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Android Academy 课程的其余内容,请升级到 CoddyKit PRO。 Android Academy 课程共包含 4 节课。

「显示摄像头预览」这节课中我会学到什么?

连接实时预览 你通过在浏览器中直接运行的动手代码来练习 Android Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Android Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Android Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「显示摄像头预览」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Android Academy 课中编写并运行代码吗?

能。每节 Android Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. CameraX 概览
  2. 显示摄像头预览
  3. 拍照
  4. 录制视频
← 返回 Android Academy