mcpField
C
camera1-to-camerax

camera1-to-camerax

github.com/android开发10.0k420 次安装2 天前更新
代码仓库

关于 camera1-to-camerax

帮助开发者将 Android 相机代码从旧版 Camera API 迁移到 CameraX,涵盖生命周期绑定、预览、对焦、拍照及切换镜头,并分别说明 Views 和 Compose 的做法。

主要功能

  1. 01配置 CameraX 依赖
  2. 02移除旧版相机代码
  3. 03绑定生命周期用例
  4. 04Views 与 Compose 预览
  5. 05点击对焦与拍照
  6. 06切换前后摄像头

适用场景

  1. 01将应用从 android.hardware.Camera 迁移
  2. 02为 Views 或 Compose 添加 CameraX 预览
  3. 03实现点击对焦和拍照

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/android/skills --skill camera1-to-camerax

安装后在 Claude Code 中输入/camera1-to-camerax确认已加载。

详细介绍

第 0 步:添加依赖

检查并添加所需的 CameraX 依赖项。为实现互操作性,请使用 1.3.0 或更高版本;如需使用 Compose 扩展,请使用 1.5.0 或更高版本。

如果使用版本目录(libs.versions.toml),请添加以下内容:

[versions]
camerax = "<minimum_version_needed>"

[libraries]
androidx-camera-core = { group = "androidx.camera", name = "camera-core", version.ref = "camerax" }
androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camerax" }
androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camerax" }
androidx-camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "camerax" }
androidx-camera-compose = { group = "androidx.camera", name = "camera-compose", version.ref = "camerax" }

并在 build.gradle.kts(或 build.gradle)中添加:

implementation(libs.androidx.camera.core)
implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.lifecycle)
implementation(libs.androidx.camera.view)
implementation(libs.androidx.camera.compose)

如果不使用版本目录,则使用以下标准 Gradle 依赖项:

implementation "androidx.camera:camera-core:<minimum_version_needed>"
implementation "androidx.camera:camera-camera2:<minimum_version_needed>"
implementation "androidx.camera:camera-lifecycle:<minimum_version_needed>"
implementation "androidx.camera:camera-view:<minimum_version_needed>"
implementation "androidx.camera:camera-compose:<minimum_version_needed>"

第 1 步:移除旧版实现

  1. 删除所有 android.hardware.Camera 实例。
  2. 删除 SurfaceView 和 SurfaceHolder.Callback 实现(surfaceCreated、surfaceChanged、surfaceDestroyed)。
  3. 移除在 onResume 或 onPause 中打开或释放相机的自定义生命周期处理逻辑。
  4. 移除手动计算方向的矩阵。

第 2 步:初始化 ProcessCameraProvider

请求 ProcessCameraProvider,并将用例绑定到 Activity 或 Fragment 生命周期。

val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(context, lifecycleOwner) {
  val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
  cameraProviderFuture.addListener({
      val cameraProvider = cameraProviderFuture.get()

      val cameraSelector = CameraSelector.Builder()
        .requireLensFacing(CameraSelector.LENS_FACING_BACK)
        .build()

      val preview = Preview.Builder().build()
      val imageCapture = ImageCapture.Builder()
        .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
        .build()

      cameraProvider.unbindAll() // Unbind before rebinding

      val camera = cameraProvider.bindToLifecycle(
        lifecycleOwner,
        cameraSelector,
        preview,
        imageCapture
      )
      val cameraControl = camera.cameraControl
    }, ContextCompat.getMainExecutor(context)
  )
}

第 3 步:实现预览和点击对焦

根据应用的 UI 工具包,准确选择以下一种模式:

选项 A:适用于 Android Views(旧版 XML)

使用 androidx.camera.view.PreviewView。

1. 设置预览:

preview.setSurfaceProvider(previewView.surfaceProvider)

2. 处理点击对焦:

val factory = previewView.meteringPointFactory
val point = factory.createPoint(x, y) // x, y from touch event
val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF).build()
cameraControl?.startFocusAndMetering(action)

选项 B:适用于 Jetpack Compose

使用 androidx.camera.compose.CameraXViewfinder。

1. 设置预览和 SurfaceRequest:

var surfaceRequest by remember { mutableStateOf<SurfaceRequest?>(null) }
val preview = remember {
  Preview.Builder().build().apply {
    setSurfaceProvider { request -> surfaceRequest = request }
  }
}

2. 渲染取景器:

surfaceRequest?.let { request ->
  CameraXViewfinder(
    surfaceRequest = request,
    coordinateTransformer = coordinateTransformer,
    modifier = Modifier
  )
}

3. 在 Compose 中处理点击对焦:

// Inside your tap gesture handler...
val surfaceCoords = with(coordinateTransformer) { offset.transform() }
val factory = SurfaceOrientedMeteringPointFactory(
  request.resolution.width.toFloat(),
  request.resolution.height.toFloat()
)
val point = factory.createPoint(surfaceCoords.x, surfaceCoords.y)
val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF).build()
cameraControl?.startFocusAndMetering(action)

4. 在 Compose 中更新目标旋转角度:

LaunchedEffect(configuration) {
  if (!view.isInEditMode) {
    val rotation = view.display?.rotation ?: Surface.ROTATION_0
    imageCapture.targetRotation = rotation
    preview.targetRotation = rotation
  }
}

第 4 步:拍摄照片

使用 ImageCapture 用例拍照。ImageProxy 会直接处理旋转。

imageCapture.takePicture(
  cameraExecutor,
  object : ImageCapture.OnImageCapturedCallback() {
    override fun onCaptureSuccess(image: ImageProxy) {
      val buffer = image.planes[0].buffer
      val bytes = ByteArray(buffer.remaining())
      buffer.get(bytes)
      val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)

      // Adjust rotation natively via ImageProxy
      val matrix = Matrix()
      matrix.postRotate(image.imageInfo.rotationDegrees.toFloat())
      if (lensFacing == CameraSelector.LENS_FACING_FRONT) {
        matrix.postScale(-1f, 1f) // Mirror for front camera
      }

      val rotatedBitmap = Bitmap.createBitmap(
        bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true
      )

      // MUST close proxy
      image.close()
    }

    override fun onError(exception: ImageCaptureException) {
      Log.e("CameraX", "Capture failed: ${exception.message}", exception)
    }
  }
)

第 5 步:切换相机

要在前后摄像头之间切换,请更改 CameraSelector 并重新触发 ProcessCameraProvider 逻辑。

lensFacing = if (lensFacing == CameraSelector.LENS_FACING_BACK) {
  CameraSelector.LENS_FACING_FRONT
} else {
  CameraSelector.LENS_FACING_BACK
}

限制

  • 不要手动管理相机生命周期:通过 ProcessCameraProvider 将相机绑定到 LifecycleOwner。避免在 onResume 或 onPause 中手动打开或关闭相机。
  • 不要手动计算对焦矩阵:MeteringPointFactory 会处理坐标转换,包括设备旋转偏移。避免自行实现矩阵。
  • 不要忘记关闭 ImageProxy:请记得在拍摄回调中调用 image.close()。跳过此调用会锁定拍摄管线,并中断后续拍照。
  • 不要在 Compose 代码中使用 AndroidView 包装 PreviewView:对于 Compose UI 布局,请使用 CameraXViewfinder。在 AndroidView 中编译 PreviewView 是一种较旧的备用方案,会导致尺寸调整问题。

评论

支持 Markdown