如何将 CameraView 与 Jetpack Compose 结合使用?
How can I use a CameraView with Jetpack Compose?
目前在 Compose 中没有与 CameraView(和 PreviewView)等效的东西。是否可以将其包装并以组合布局显示?
暂时没有官方
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_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()
}
}
}
仍然没有可组合的 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
})
}
我创建了一个库以在 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)
}
这是我的代码片段(基于 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)
}
}
)
}
目前在 Compose 中没有与 CameraView(和 PreviewView)等效的东西。是否可以将其包装并以组合布局显示?
暂时没有官方 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_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()
}
}
}
仍然没有可组合的 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
})
}
我创建了一个库以在 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)
}
这是我的代码片段(基于 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)
}
}
)
}