CameraX

他的特点是可以不用去申请相机等应用,而是可以直接调用硬件的相机源。当然采用这个或者调用相机都是没有问题的,这取决于你的需求

(如果你希望你的应用程序完全访问设备的相机,并且不用离开应用程序时,例如抖音,学习通)那么使用CameraX是个很好的决定。

导入包

1
2
3
4
5
6
7
8
//Camera
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. 申请权限
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. 申请权限和验证权限的代码:
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
)
}
  1. 对于使用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
)

}
  1. 因为需要传入一个控制器,所以提前在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
)
}
}
  1. 切换摄像头,需要使用到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"
)
}
  1. 拍摄照片

拍摄照片时,单独定义了一个方法,他首先需要检查权限,如果没有权限,将直接退出,不允许使用。他也是调用`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)
}
}
)
}
  1. 拍摄视频

对于视频的拍摄,无法直观的显示出来,因为视频并不像图片一样可以得到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()
}
}

}
}
}