mcpField
C
camera1-to-camerax

camera1-to-camerax

github.com/android開発10.0k420 回インストール2 日前に更新
リポジトリ

camera1-to-camerax について

Android の旧 Camera API から CameraX への移行を支援するスキルです。Views と Compose の両方で、ライフサイクルへのバインド、プレビュー、フォーカス、撮影、カメラ切り替えを案内します。

主な機能

  1. 01CameraX 依存関係の設定
  2. 02旧カメラコードの削除
  3. 03ライフサイクルへのユースケースのバインド
  4. 04Views と Compose のプレビュー
  5. 05タップフォーカスと撮影
  6. 06前面・背面カメラの切り替え

活用シーン

  1. 01android.hardware.Camera を使うアプリの移行
  2. 02Views または 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 以上を使用してください。

Version Catalog(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)

Version Catalog を使用していない場合は、標準の 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 ツールキットに応じて、次のパターンから 1 つだけ選択します。

オプション 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 コードで PreviewView を AndroidView にラップしないでください:Compose の UI レイアウトでは、CameraXViewfinder を使用します。PreviewView を AndroidView 内で使用するのは、サイズ変更の問題を引き起こす古い代替手段です。

コメント