CameraX
他的特点是可以不用去申请相机等应用,而是可以直接调用硬件的相机源。当然采用这个或者调用相机都是没有问题的,这取决于你的需求
(如果你希望你的应用程序完全访问设备的相机,并且不用离开应用程序时,例如抖音,学习通)那么使用CameraX
是个很好的决定。
导入包
1 2 3 4 5 6 7 8
| val cameraxVersion = "1.3.0-rc01" implementation("androidx.camera:camera-core:$cameraxVersion") implementation("androidx.camera:camera-camera2:$cameraxVersion") implementation("androidx.camera:camera-lifecycle:$cameraxVersion") implementation("androidx.camera:camera-video:$cameraxVersion") implementation("androidx.camera:camera-view:$cameraxVersion") implementation("androidx.camera:camera-extensions:$cameraxVersion")
|
- 申请权限
1 2 3 4 5 6 7
| //这将会声明一个权限请求,它可以被用户看到 <uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
- 申请权限和验证权限的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| if (!hasRequiredPermissions()) { ActivityCompat.requestPermissions( this, CAMERAX_PERMISSIONS, 0 ) }
private fun hasRequiredPermissions(): Boolean { return CAMERAX_PERMISSIONS.all { ContextCompat.checkSelfPermission( applicationContext, it ) == PackageManager.PERMISSION_GRANTED } }
companion object { private val CAMERAX_PERMISSIONS = arrayOf( Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO ) }
|
- 对于使用CameraX,他当前并不能直接通过开箱即用的compose来使用。而是需要使用到view的方式来实现。
通过PreviewView来实现拍摄界面关键需要接受一个摄像头控制器:它提供对部分/所有设备摄像头的访问,允许将摄像头的生命周期 (打开/关闭时)附加到 lifecycleOwner ,并具有应用程序进程的范围 (它是一个单例)。 就如何使用它而言,它是CameraX中的一种低级应用编程接口
在某种意义上说,当你使用它时,你需要初始化它,创建和配置你的用例 (即 Preview , ImageAnalysis , ImageCapture),将它们绑定到 LifecycleOwner
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Composable fun CameraPreview( controller: LifecycleCameraController, modifier: Modifier = Modifier ) { val lifecycleOwner = LocalLifecycleOwner.current AndroidView( factory = { PreviewView(it).apply { this.controller = controller controller.bindToLifecycle(lifecycleOwner) } }, modifier = modifier )
}
|
- 因为需要传入一个控制器,所以提前在activity使用remember或者直接在viewModel中声明一个controller
1 2 3 4 5 6 7 8 9
| val controller = remember { LifecycleCameraController(applicationContext).apply { setEnabledUseCases( CameraController.IMAGE_CAPTURE or CameraController.VIDEO_CAPTURE ) } }
|
- 切换摄像头,需要使用到
camereSelector
这一属性:
1 2 3 4 5 6 7 8 9 10 11
| IconButton(onClick = { controller.cameraSelector = if (controller.cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) { CameraSelector.DEFAULT_FRONT_CAMERA } else CameraSelector.DEFAULT_BACK_CAMERA }, modifier = Modifier.offset(16.dp, 16.dp)) { Icon( imageVector = Icons.Default.Cameraswitch, contentDescription = "Switch Camera" ) }
|
- 拍摄照片
拍摄照片时,单独定义了一个方法,他首先需要检查权限,如果没有权限,将直接退出,不允许使用。他也是调用`controller的拍摄方法,他需要获取主线程,然后设置一个拍摄后的回调函数来实现拍照并存储
ContextCompat 可以理解为是封装了 Context 的一些便捷方法,如加载图片等资源文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| private fun takePhoto( controller: LifecycleCameraController, onPhotoTaken: (Bitmap) -> Unit ) { if (!hasRequiredPermissions()) { return } controller.takePicture( ContextCompat.getMainExecutor(applicationContext), object : OnImageCapturedCallback() { override fun onCaptureSuccess(image: ImageProxy) { super.onCaptureSuccess(image) val matrix = Matrix().apply { postRotate(image.imageInfo.rotationDegrees.toFloat()) postScale(-1f, 1f) } val rotatedBitmap = Bitmap.createBitmap( image.toBitmap(), 0, 0, image.width, image.height, matrix, true ) onPhotoTaken(image.toBitmap()) }
override fun onError(exception: ImageCaptureException) { super.onError(exception) Log.e("Camera", "Couldn't take photo:", exception) } } ) }
|
- 拍摄视频
对于视频的拍摄,无法直观的显示出来,因为视频并不像图片一样可以得到bitmap然后直接在内存中存储。因此,在这里我们选择存储到内部存储中。而对于手机而言,只要不是放到公共的空间。是不需要申请存储空间的访问的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| private var recording: Recording? = null
@SuppressLint("MissingPermission") private fun recordVideo(controller: LifecycleCameraController) { if (recording != null) { recording?.stop() recording = null return } if (!hasRequiredPermissions()) { return } val outputFile = File(filesDir, "my-recording.mp4") recording = controller.startRecording( FileOutputOptions.Builder(outputFile).build(), AudioConfig.create(true), ContextCompat.getMainExecutor(applicationContext) ) { event -> when (event) { is VideoRecordEvent.Finalize -> { if (event.hasError()) { recording?.close() recording = null Toast.makeText( applicationContext, "Video capture failed", Toast.LENGTH_LONG ).show()
} else { Toast.makeText( applicationContext, "Video capture succeeded", Toast.LENGTH_LONG ).show() } }
} } }
|