如何在Jetpack Compose中使用CameraView?

16

目前在Compose中没有CameraView(和PreviewView)的等效物。是否有可能将其包装并显示在Compose布局中?


我自己没有尝试过,但是有一个webView的示例: https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/ui/ui-android-view/src/main/java/androidx/ui/androidview/WebComponent.kthttps://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/WebComponentActivity.kt - Habib Kazemi
请问您能具体说明一下吗? - nyx69
@pentexnyx,我添加了一个更详细的答案。 - Habib Kazemi
@HabibKazemi很棒(点赞)-但我正在和icefex交谈(尽管我没有提到他,我的错)。 - nyx69
4个回答

13

仍然没有CameraX组件。你需要使用AndroidView来创建一个。

针对Compose 1.0.0-beta02的更新示例:

@Composable
fun CameraPreview(
    modifier: Modifier = Modifier,
    cameraSelector: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA,
    scaleType: PreviewView.ScaleType = PreviewView.ScaleType.FILL_CENTER,
) {
    val lifecycleOwner = LocalLifecycleOwner.current
    AndroidView(
        modifier = modifier,
        factory = { context ->
            val previewView = PreviewView(context).apply {
                this.scaleType = scaleType
                layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT
                )
                // Preview is incorrectly scaled in Compose on some devices without this
                implementationMode = PreviewView.ImplementationMode.COMPATIBLE
            }

            val cameraProviderFuture = ProcessCameraProvider.getInstance(context)

            cameraProviderFuture.addListener({
                val cameraProvider = cameraProviderFuture.get()

                // Preview
                val preview = Preview.Builder()
                    .build()
                    .also {
                        it.setSurfaceProvider(previewView.surfaceProvider)
                    }

                try {
                    // Must unbind the use-cases before rebinding them.
                    cameraProvider.unbindAll()

                    cameraProvider.bindToLifecycle(
                        lifecycleOwner, cameraSelector, preview
                    )
                } catch (exc: Exception) {
                    Log.e(TAG, "Use case binding failed", exc)
                }
            }, ContextCompat.getMainExecutor(context))

            previewView
        })
}

这与我希望找到的差不多。有一个问题:ProcessCameraProvider 似乎未定义。根据参考文档 here,我已经包含了 androidx.camera:camera-lifecycle:1.1.0-alpha3。你使用的是哪个库和版本来获取 ProcessCameraProvider?谢谢! - Victor Ude
1
我的依赖关系如下:implementation "androidx.camera:camera-camera2:1.0.0-rc04" implementation "androidx.camera:camera-lifecycle:1.0.0-rc04" implementation "androidx.camera:camera-view:1.0.0-alpha23"除此之外,还有很多其他的AndroidX依赖项,但我认为这些就足够了。我想1.1.0版本也应该可以,但我还没有尝试过。 - Sean
谢谢。我刚刚从Maven中使用了1.1.0-alpha03版本,现在已经可以工作了,我准备回来报告。这真是个美好的时代! :)https://mvnrepository.com/artifact/androidx.camera/camera-lifecycle - Victor Ude

10

目前CameraX还没有官方的可组合函数,因此我们必须在组合中膨胀遗留的Android视图。

要实现这一点,我们可以使用AndroidView可组合函数, 它接受两个参数

  • @param resId要膨胀的布局资源的ID。
  • @param postInflationCallback布局膨胀后要调用的回调。

为了访问生命周期和上下文,我们使用了环境变量。

val lifecycleOwner = LifecycleOwnerAmbient.current
val context = ContextAmbient.current

既然我们已经准备好了所有需要的东西,那就开始吧:

创建一个名为camera_host.xml的布局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.camera.view.PreviewView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/previewView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

使用AndroidView组合函数膨胀它。

@Composable
fun SimpleCameraPreview() {
    val lifecycleOwner = LifecycleOwnerAmbient.current
    val context = ContextAmbient.current
    val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
    AndroidView(resId = R.layout.camera_host) { inflatedLayout ->
       //You can call
      // findViewById<>() and etc ... on inflatedLayout
      // here PreviewView is the root of my layout so I just cast it to
      // the PreviewView and no findViewById is required

        cameraProviderFuture.addListener(Runnable {
            val cameraProvider = cameraProviderFuture.get()
            bindPreview(
                 lifecycleOwner,
                 inflatedLayout as PreviewView /*the inflated layout*/,
                 cameraProvider)
        }, ContextCompat.getMainExecutor(context))

    }
}

fun bindPreview(
    lifecycleOwner: LifecycleOwner,
    previewView: PreviewView,
    cameraProvider: ProcessCameraProvider
) {
    var preview: Preview = Preview.Builder().build()

    var cameraSelector: CameraSelector = CameraSelector.Builder()
        .requireLensFacing(CameraSelector.LENS_FACING_BACK)
        .build()

    preview.setSurfaceProvider(previewView.createSurfaceProvider())

    var camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)
}
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            SimpleCameraPreview()
        }
    }
}

6

这是我的片段(基于Sean的答案),它还处理了torch状态和资源配置,并专注于tap逻辑。 依赖项:

implementation 'androidx.camera:camera-camera2:1.1.0-alpha11'
implementation 'androidx.camera:camera-view:1.0.0-alpha31'
implementation 'androidx.camera:camera-lifecycle:1.1.0-alpha11'

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.0-RC'

@Composable
fun CameraPreview(
    modifier: Modifier = Modifier,
    cameraSelector: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA,
    implementationMode: PreviewView.ImplementationMode = PreviewView.ImplementationMode.COMPATIBLE,
    scaleType: PreviewView.ScaleType = PreviewView.ScaleType.FILL_CENTER,
    imageAnalysis: ImageAnalysis? = null,
    imageCapture: ImageCapture? = null,
    preview: Preview = remember { Preview.Builder().build() },
    enableTorch: Boolean = false,
    focusOnTap: Boolean = false
) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current

    val cameraProvider by produceState<ProcessCameraProvider?>(initialValue = null) {
        value = ProcessCameraProvider.getInstance(context).await()
    }

    // TODO: add cameraSelector
    val camera = remember(cameraProvider) {
        cameraProvider?.let {
            it.unbindAll()
            it.bindToLifecycle(
                lifecycleOwner,
                cameraSelector,
                *listOfNotNull(imageAnalysis, imageCapture, preview).toTypedArray()
            )
        }
    }

    LaunchedEffect(camera, enableTorch) {
        camera?.let {
            if (it.cameraInfo.hasFlashUnit()) {
                it.cameraControl.enableTorch(enableTorch).await()
            }
        }
    }

    DisposableEffect(Unit) {
        onDispose {
            cameraProvider?.unbindAll()
        }
    }

    AndroidView(
        modifier = modifier.pointerInput(camera, focusOnTap) {
            if (!focusOnTap) return@pointerInput

            detectTapGestures {
                val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
                    size.width.toFloat(),
                    size.height.toFloat()
                )

                val meteringAction = FocusMeteringAction.Builder(
                    meteringPointFactory.createPoint(it.x, it.y),
                    FocusMeteringAction.FLAG_AF
                ).disableAutoCancel().build()

                camera?.cameraControl?.startFocusAndMetering(meteringAction)
            }
        },
        factory = { _ ->
            PreviewView(context).also {
                it.scaleType = scaleType
                it.implementationMode = implementationMode
                preview.setSurfaceProvider(it.surfaceProvider)
            }
        }
    )
}

谢谢,这真的很有帮助,可以在不重新合成的情况下启用手电筒! - Antonis Radz

1

我创建了一个库,用于在Jetpack Compose中使用CameraX。在官方库发布前,这可能会很有用。

https://github.com/skgmn/CameraXX

在你的build.gradle中,(需要GitHub个人访问令牌)
implementation "com.github.skgmn:cameraxx-composable:0.3.0"

可组合的方法签名
CameraPreview(
    modifier: Modifier = Modifier,
    cameraSelector: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA,
    preview: Preview?,
    imageCapture: ImageCapture? = null,
    imageAnalysis: ImageAnalysis? = null
)

你可以省略preview参数来使用默认的Preview实例。
一个例子。
class MainViewModel : ViewModel() {
    val imageCapture = ImageCapture.Builder().build()
}

@Composable
fun Main() {
    val viewModel: MainViewModel = viewModel()
    val imageCapture by remember { viewModel.imageCapture }

    CameraPreview(Modifier.fillMaxSize(), imageCapture)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接