0Pricing
Android Academy · 课时

拍照

捕获并保存图片

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

拍摄照片

屏幕上显示预览后,下一步就是拍照。CameRX 通过 ImageCapture 用例处理这项功能。

您将把 ImageCapture 与预览一起绑定,然后在用户点击快门按钮时调用 takePicture(),并将结果保存到设备图库。

构建 ImageCapture 用例

ImageCapture 通过 builder 进行配置。一个实用选项是拍摄模式,它用于在延迟和质量之间进行权衡:

  • CAPTURE_MODE_MINIMIZE_LATENCY——快门响应更快。
  • CAPTURE_MODE_MAXIMIZE_QUALITY——图像质量最佳,但速度稍慢。
val imageCapture = ImageCapture.Builder()
    .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
    .build()

绑定 Preview + ImageCapture

您可以一次绑定多个用例。将 preview 和 imageCapture 都传给 bindToLifecycle,这样相机既能提供实时画面,也能随时拍照。

cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
    lifecycleOwner,
    CameraSelector.DEFAULT_BACK_CAMERA,
    preview,
    imageCapture
)

保存位置:MediaStore

通过 MediaStore 保存照片,是现代且适合分区存储的方式。您可以使用 ContentValues 描述文件(名称、MIME 类型和文件夹),然后让系统将其放入共享的 Pictures 集合中。

在 Android 10 及更高版本上,这种方式无需旧版 WRITE_EXTERNAL_STORAGE 权限。

val name = "photo_${System.currentTimeMillis()}"
val contentValues = ContentValues().apply {
    put(MediaStore.MediaColumns.DISPLAY_NAME, name)
    put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
    put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/MyApp")
}

输出选项

要告诉 CameraX 将 JPEG 写入何处,请使用内容解析器、目标集合 URI 和您的 ContentValues 构建 OutputFileOptions。

val outputOptions = ImageCapture.OutputFileOptions
    .Builder(
        context.contentResolver,
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        contentValues
    )
    .build()

调用 takePicture()

现在触发拍摄。takePicture() 接收输出选项、一个 Executor 和一个回调。CameRX 会异步报告成功或错误。

请使用 ContextCompat.getMainExecutor(context),确保回调在主线程上运行。

imageCapture.takePicture(
    outputOptions,
    ContextCompat.getMainExecutor(context),
    object : ImageCapture.OnImageSavedCallback {
        override fun onImageSaved(output: ImageCapture.OutputFileResults) {
            val savedUri = output.savedUri
            // Photo saved! Show a confirmation.
        }
        override fun onError(exc: ImageCaptureException) {
            // Handle the failure.
        }
    }
)

处理结果

成功时,OutputFileResults.savedUri 会提供已保存图像的 URI。您可以使用 Coil 显示它、分享它,或显示 Snackbar 来确认保存成功。

发生错误时,ImageCaptureException 会告知您具体原因——请记录错误并通知用户。

override fun onImageSaved(output: ImageCapture.OutputFileResults) {
    val uri = output.savedUri ?: return
    Toast.makeText(context, "Saved: $uri", Toast.LENGTH_SHORT).show()
}

连接快门按钮

在 Compose 中,您可以使用 Box 将快门按钮叠加在预览上方。请将 ImageCapture 引用提升到更高层级,这样按钮的 onClick 就能调用 takePhoto。

Box(modifier = Modifier.fillMaxSize()) {
    CameraPreview(imageCapture = imageCapture)
    IconButton(
        onClick = { takePhoto(context, imageCapture) },
        modifier = Modifier
            .align(Alignment.BottomCenter)
            .padding(32.dp)
    ) {
        Icon(Icons.Default.PhotoCamera, contentDescription = "Take photo")
    }
}

提取 takePhoto()

将拍摄逻辑移到辅助函数中,让可组合函数保持简洁。该函数接收上下文和 ImageCapture 用例,构建输出选项并调用 takePicture。

fun takePhoto(context: Context, imageCapture: ImageCapture) {
    val values = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, "IMG_${System.currentTimeMillis()}")
        put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
    }
    val options = ImageCapture.OutputFileOptions.Builder(
        context.contentResolver,
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        values
    ).build()

    imageCapture.takePicture(
        options,
        ContextCompat.getMainExecutor(context),
        object : ImageCapture.OnImageSavedCallback {
            override fun onImageSaved(o: ImageCapture.OutputFileResults) {}
            override fun onError(e: ImageCaptureException) {}
        }
    )
}

闪光灯与旋转

最后再完成两项设置:

  • 闪光灯:将 imageCapture.flashMode 设置为 FLASH_MODE_ON、OFF 或 AUTO。
  • 旋转:CameRX 会使用目标旋转角度,正确设置已保存 JPEG 的方向;请及时更新该值,以确保方向准确。
imageCapture.flashMode = ImageCapture.FLASH_MODE_AUTO
// Update target rotation when the device rotates:
imageCapture.targetRotation = display.rotation

整合所有功能

完整的拍照流程如下:构建 ImageCapture → 将其与预览绑定 → 点按快门时,构建 OutputFileOptions 并调用 takePicture → 处理 onImageSaved / onError。

只需几行代码,您就能完成一个可将照片保存到图库的完整相机。

快速检查

请确认您已理解拍照流程。

回顾

现在,您已经可以使用 CameRX 拍摄并保存照片:

  • 构建 ImageCapture 用例,并将其与预览绑定。
  • 使用 ContentValues 和 MediaStore 描述输出,以使用分区存储。
  • 将 OutputFileOptions、执行器和 OnImageSavedCallback 传给 takePicture()。
  • 成功时读取 savedUri;发生错误时处理 ImageCaptureException。

接下来,您将使用 VideoCapture 用例录制视频。

常见问题解答

「拍照」课时是免费的吗?

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

「拍照」这节课中我会学到什么?

捕获并保存图片 你通过在浏览器中直接运行的动手代码来练习 Android Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Android Academy 需要有经验吗?

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

「拍照」课时需要多长时间?

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

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

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

此课程中的所有课时

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