背景
由于Jetpack Compose提升了开发效率和应用的性能,Jetpack Compose 已经被用在越来越多的应用中。CameraX相对于之前版本的相机库也有很大的性能提升,更加方便于调用和操作,下面我们简单的说一说如何在Compose中调用相机进行预览,并进行拍照。
- 先简要的说明一下需要的相关依赖(Compose和CameraX)
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.ui:ui-viewbinding:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
// The following line is optional, as the core library is included indirectly by camera-camera2
implementation "androidx.camera:camera-core:$camerax_version"
implementation "androidx.camera:camera-camera2:$camerax_version"
// If you want to additionally use the CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// If you want to additionally use the CameraX View class
implementation "androidx.camera:camera-view:$camerax_version"
// If you want to additionally use the CameraX Extensions library
implementation "androidx.camera:camera-extensions:$camerax_version"
- 提供相机对象; 使用PreviewView提供相机预览;
2.1. application中对CameraX配置
2.2. 提供CameraX的相关扩展方法class MyApplication : Application(), CameraXConfig.Provider { //这里标注只使用后摄像头,按需配置 override fun getCameraXConfig(): CameraXConfig { return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig()) .setMinimumLoggingLevel(Log.ERROR) .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA) .build() } }
2.3. 提供Compose中使用的相机预览View/** * 提供相机对象 * @param onProvide 提供回调方法 */ fun Context.provideCameraProvider(onProvide: (ProcessCameraProvider?) -> Unit) { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener({ var processCameraProvider: ProcessCameraProvider? = null try { processCameraProvider = cameraProviderFuture.get() } catch (e: Exception) { HiLogger.singleton().printStackTrace(e) } onProvide.invoke(processCameraProvider) }, ContextCompat.getMainExecutor(this)) } /** 拍照功能绑定到生命周期 **/ @SuppressLint("RestrictedApi") fun PreviewView.imageCapture( owner: LifecycleOwner, provider: ProcessCameraProvider? ): ImageCapture { val viewPort = ViewPort.Builder(Rational(width, height), display.rotation).build() val preview = Preview.Builder() .setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA) .build() .also { it.setSurfaceProvider(surfaceProvider) } val cameraSelector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK) .build() val imageCapture = ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build() val useCaseGroup = UseCaseGroup.Builder() .addUseCase(preview) .addUseCase(imageCapture) .setViewPort(viewPort) .build() provider?.unbindAll() //防止相机未解绑而发生闪退 provider?.bindToLifecycle(owner, cameraSelector, useCaseGroup) return imageCapture }
2.4. 调用示例/** * 相机预览视图 * @param modifier 修饰器 * @param cameraPictureFile 相机拍下来的图片 * @param isPreviewEnable 是否开启预览 * @param cameraProvider 相机提供对象 * @param onStartPreview 开始预览 * @param onTakePicture 拍照操作 */ @Composable @Preview(name = "效果预览", widthDp = 240, heightDp = 150) fun CameraPreviewView( modifier: Modifier = Modifier, cameraProvider: ProcessCameraProvider? = null, cameraPictureFile: File? = null, isPreviewEnable: Boolean = false, onStartPreview: () -> Unit = {}, onTakePicture: (ImageCapture?) -> Unit = {}, ) = Box(modifier.clip(ShunYunTongTheme.shapes.small)) { val localContext = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current val previewView = remember { PreviewView(localContext).apply { implementationMode = PreviewView.ImplementationMode.PERFORMANCE } } var cameraPreviewRunning by remember { mutableStateOf(false) } var imageCapture by remember { mutableStateOf<ImageCapture?>(null) } LaunchedEffect(isPreviewEnable, cameraProvider) { if (isPreviewEnable && cameraProvider != null) { try { imageCapture = previewView.imageCapture(lifecycleOwner, cameraProvider) cameraPreviewRunning = true } catch (e: Exception) { HiLogger.singleton().printStackTrace(e) } } else cameraPreviewRunning = false } AndroidView( modifier = Modifier .fillMaxSize() .background(Color.White), factory = { previewView }) if (!isPreviewEnable) Image( modifier = Modifier .fillMaxSize() .click { onStartPreview() }, contentDescription = null, contentScale = ContentScale.Crop, painter = imagePainter(cameraPictureFile ?: R.mipmap.ic_launcher), ) Text( modifier = Modifier .align(Alignment.BottomCenter) .padding(bottom = dimensionResource(R.dimen.dp_12)) .wrapContentSize() .clip(AppTheme.shapes.large) .background(Color.White) .border(1.dp, Color.LightGray, AppTheme.shapes.large) .click { if (cameraProvider == null || !cameraPreviewRunning) onStartPreview() else onTakePicture(imageCapture) } .padding( vertical = dimensionResource(R.dimen.dp_4), horizontal = dimensionResource(R.dimen.dp_12), ), text = when { isPreviewEnable -> "点击拍照" else -> if (cameraPictureFile == null) "开启预览" else "重新拍照" }, style = TextStyle( color = Color.Black, fontWeight = FontWeight.Bold, fontSize = fontDimensionResource(R.dimen.sp_15) ) ) }
class MyActivity : BaseActivity { private val viewModel by viewModels<MyViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { CameraPreviewView( modifier = Modifier .padding(dimensionResource(R.dimen.dp_16)) .fillMaxWidth() .aspectRatio(16f / 9f) .clip(AppTheme.shapes.small), cameraProvider = cameraProvider, isPreviewEnable = isPreviewEnable, cameraPictureFile = cameraPictureFile, onStartPreview = onStartPreview, onTakePicture = onTakePicture ) } provideCameraProvider { provider -> if (provider == null) return@provideCameraProvider viewModel.applyNewState { cameraProvider = provider } } } }
结尾
以上就是一个完整的调用示例了,拍照需要使用ImageCapture捕获照片,如下:
fun takePicture(
context: Context,
imageCapture: ImageCapture?,
destImageFile: File,
onStart: () -> Unit = {},
onError: (Throwable) -> Unit = {},
onSuccess: (File?) -> Unit,
) {
onStart.invoke()
if (imageCapture == null) {
onError.invoke(Throwable("ImageCapture对象为空"))
return
}
destImageFile.parentFile?.mkdirs()
val executor = ContextCompat.getMainExecutor(context)
val outputOptions = ImageCapture.OutputFileOptions
.Builder(destImageFile)
.build()
val savedCallback = object : ImageCapture.OnImageSavedCallback {
@SuppressLint("RestrictedApi")
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
onSuccess.invoke(outputOptions.file)
}
override fun onError(exception: ImageCaptureException) {
onError.invoke(exception)
}
}
imageCapture.takePicture(outputOptions, executor, savedCallback)
}
*希望对初学者有所帮助,不懂的可以留言,如需转载请注明原文出处!