如何将 CameraView 与 Jetpack Compose 一起使用?

Posted

技术标签:

【中文标题】如何将 CameraView 与 Jetpack Compose 一起使用?【英文标题】:How can I use a CameraView with Jetpack Compose? 【发布时间】:2020-08-30 21:04:59 【问题描述】:

目前 Compose 中没有相当于 CameraView(和 PreviewView)的功能。是否可以将其包装并显示在撰写布局中?

【问题讨论】:

我自己没试过,但是有webView的样例:android.googlesource.com/platform/frameworks/support/+/…android.googlesource.com/platform/frameworks/support/+/… 你能具体说明一下吗? @pentexnyx 我添加了一个包含更多细节的答案。 @HabibKazemi 伟大的一个(赞成)-但我正在和 icefex 交谈(不过我没有提到他,我的错) 【参考方案1】:

仍然没有可组合的 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 使用什么库和版本?谢谢! 我的依赖是:实现“androidx.camera:camera-camera2:1.0.0-rc04”实现“androidx.camera:camera-lifecycle:1.0.0-rc04”实现“androidx.camera: camera-view:1.0.0-alpha23" 以及许多其他 androidx 依赖项,但我认为那些应该这样做。我想 1.1.0 也可以,但我还没有尝试过。 谢谢。我刚刚得到它与来自 Maven 的1.1.0-alpha03 一起工作,并且正在返回报告的路上。什么时候活着! :) mvnrepository.com/artifact/androidx.camera/camera-lifecycle【参考方案2】:

目前没有任何官方 CameraX 的可组合函数,因此我们必须在 compose 中扩展旧版 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_
    android:layout_ />

并使用AndroidView Composable 函数对其进行膨胀。

@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()
        
    

【讨论】:

【参考方案3】:

我创建了一个库以在 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)

【讨论】:

【参考方案4】:

这是我的 sn-p(基于 Sean 的回答),它还处理手电筒状态和资源配置,并增加了对点击逻辑的关注。 依赖:

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)
            
        
    )

【讨论】:

以上是关于如何将 CameraView 与 Jetpack Compose 一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

如何安装CameraView

CameraView 中的人像模式

在android中的surfaceview上创建cameraview(掩码)

在 CameraView 上应用 Live GpuImage

Kotlin/Anko/OpenCV/CameraView 无法创建处理程序,因为线程尚未调用 Looper.prepare()

如何在 QQuickPaintedItem 中以有效的方式绘制顺序图像