你怎么用camerax拍照?
How do you take a picture with camerax?
我仍在练习 Kotlin 和 Android 开发。据我所知,Camera class 已被弃用,Android 邀请使用 Camerax,因为这个高级 class 是独立于设备的,并且它们使在应用程序上实现相机的过程。
我试图阅读文档 (https://developer.android.com/training/camerax),但它写得太糟糕了,我几乎无法理解他们试图解释的内容。
所以我去阅读了文档中给出的完整示例代码 (https://github.com/android/camera-samples/tree/main/CameraXBasic)。
CameraFragment 代码长约 500 行(忽略导入和各种注释)。
我真的需要写 500 行代码来简单地拍照吗?
这怎么算是“比以前简单了”?
我的意思是,Android 编程时我只需要编写 4 行代码就可以让用户从他的存储中 select 一个图像并检索并显示它在 ImageView 中。
有没有真正简单的拍照方法,或者我真的需要停下来浪费一整天的工作来编写所有这些行代码?
编辑:
获取文档的这一页:
https://developer.android.com/training/camerax/architecture#kotlin
从这段代码开始。
val preview = Preview.Builder().build()
val viewFinder: PreviewView = findViewById(R.id.previewView)
// The use case is bound to an Android Lifecycle with the following code
val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)
cameraProvider 不知从何而来。这应该是什么?我发现它是一个 ProcessCameraProvider,但我该如何初始化它呢?
它应该是一个 lateinit var 还是已经在其他地方初始化了?
因为如果我尝试写 val cameraProvider = ProcessCameraProvider()
我会得到一个错误,那我该怎么办?
什么是 cameraSelector 参数?之前没有定义。我发现它是 selector 用于前置或后置摄像头,但我应该如何通过阅读文档的那一页来了解它?
这份文件怎么能发布有这些缺陷呢?
一个人应该如何轻松学习?
在使用 CameraX 与设备的相机交互之前,您需要初始化库。初始化过程是异步的,涉及加载有关设备相机的信息。
您使用 ProcessCameraProvider
与设备的摄像头进行交互。它是一个 Singleton,所以当您第一次获得 if 的实例时,CameraX 会执行它的初始化。
val cameraProviderFuture: ListenableFuture<ProcessCameraProvider> = ProcessCameraProvider.getInstance(context)
获取 ProcessCameraProvider
单例 return 是 Future
因为它可能需要异步初始化库。第一次获取它时,可能需要一些时间(通常不到一秒),但后续调用会立即 return,因为初始化已经执行。
有了ProcessCameraProvider
,您就可以开始与设备的摄像头互动了。您可以使用 CameraSelector
选择要与之交互的相机,它为您要使用的相机包装了一组滤镜。通常,如果您只是想使用主后置或前置摄像头,您会使用 CameraSelector.DEFAULT_BACK_CAMERA
或 CameraSelector.DEFAULT_FRONT_CAMERA
.
现在您已经定义了要使用的相机,您可以构建所需的用例。例如,您想要拍照,因此您将使用 ImageCapture
用例。它允许使用相机拍摄单个捕获帧(通常是高质量帧),并将其作为原始缓冲区提供,或将其存储在文件中。要使用它,您可以根据需要对其进行配置,或者您可以让 CameraX 使用默认配置。
val imageCapture = ImageCapture.Builder().build()
在 CameraX 中,相机的生命周期由 LifecycleOwner
控制,这意味着当 LifecycleOwner
的生命周期开始时,相机打开,当它停止时,相机关闭。因此,您需要选择一个可以控制相机的生命周期。如果您使用 Activity
,您通常希望相机在 Activity
启动时启动,并在它停止时停止,因此您将使用 Activity
实例本身作为LifecycleOwner
,如果您使用的是 Fragment
,您可能希望使用它的视图生命周期 (Fragment.getViewLifecycleOwner()
)。
最后,您需要将拼图的各个部分拼在一起。
processCameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
imageCapture
)
应用程序通常包含一个显示相机预览的取景器,因此您可以使用 Preview
用例,并将其与 ImageCapture
用例绑定。 Preview
用例允许将相机帧流式传输到 Surface
。由于设置 Surface
并在其上正确绘制预览可能很复杂,CameraX 提供了 PreviewView
,一个 View
可以与 Preview
用例一起使用来显示相机预览。您可以查看如何使用它们 here.
// Just like ImageCapture, you can configure the Preview use case if you'd wish.
val preview = Preview.Builder().build()
// Provide PreviewView's Surface to CameraX. The preview will be drawn on it.
val previewView: PreviewView = findViewById(...)
preview.setSurfaceProvider(previewView.surfaceProvider)
// Bind both the Preview and ImageCapture use cases
processCameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
imageCapture,
preview
)
现在要真正拍照,您可以使用 ImageCapture
的 takePicture
方法之一。一个提供捕获图像的 JPEG 原始缓冲区,另一个将其保存在您提供的文件中(如果需要,请确保您具有必要的存储权限)。
imageCapture.takePicture(
ContextCompat.getMainExecutor(context), // Defines where the callbacks are run
object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(imageProxy: ImageProxy) {
val image: Image = imageProxy.image // Do what you want with the image
imageProxy.close() // Make sure to close the image
}
override fun onError(exception: ImageCaptureException) {
// Handle exception
}
}
)
val imageFile = File("somePath/someName.jpg") // You can store the image in the cache for example using `cacheDir.absolutePath` as a path.
val outputFileOptions = ImageCapture.OutputFileOptions
.Builder(imageFile)
.build()
takePicture(
outputFileOptions,
CameraXExecutors.mainThreadExecutor(),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
}
override fun onError(exception: ImageCaptureException) {
}
}
)
Do I really need to write 500 lines of code to simply take a picture?
How is this supposed to be considered "simpler than before"?
CameraXBasic 并不像其名称所暗示的那样“基本”x) 它更像是 CameraX 的 3 个用例的完整示例。尽管 CameraFragment
很长,但它很好地解释了事情,因此每个人都更容易理解。
CameraX“比以前更简单”,之前主要指的是Camera2,起码上手有点难度。 CameraX 通过其使用用例的方法提供了对开发人员更友好的 API。它还处理兼容性,这在以前是个大问题。确保您的相机应用在大多数 Android 设备上可靠地工作非常具有挑战性。
我仍在练习 Kotlin 和 Android 开发。据我所知,Camera class 已被弃用,Android 邀请使用 Camerax,因为这个高级 class 是独立于设备的,并且它们使在应用程序上实现相机的过程。
我试图阅读文档 (https://developer.android.com/training/camerax),但它写得太糟糕了,我几乎无法理解他们试图解释的内容。 所以我去阅读了文档中给出的完整示例代码 (https://github.com/android/camera-samples/tree/main/CameraXBasic)。 CameraFragment 代码长约 500 行(忽略导入和各种注释)。
我真的需要写 500 行代码来简单地拍照吗? 这怎么算是“比以前简单了”?
我的意思是,Android 编程时我只需要编写 4 行代码就可以让用户从他的存储中 select 一个图像并检索并显示它在 ImageView 中。 有没有真正简单的拍照方法,或者我真的需要停下来浪费一整天的工作来编写所有这些行代码?
编辑: 获取文档的这一页: https://developer.android.com/training/camerax/architecture#kotlin 从这段代码开始。
val preview = Preview.Builder().build()
val viewFinder: PreviewView = findViewById(R.id.previewView)
// The use case is bound to an Android Lifecycle with the following code
val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)
cameraProvider 不知从何而来。这应该是什么?我发现它是一个 ProcessCameraProvider,但我该如何初始化它呢?
它应该是一个 lateinit var 还是已经在其他地方初始化了?
因为如果我尝试写 val cameraProvider = ProcessCameraProvider()
我会得到一个错误,那我该怎么办?
什么是 cameraSelector 参数?之前没有定义。我发现它是 selector 用于前置或后置摄像头,但我应该如何通过阅读文档的那一页来了解它?
这份文件怎么能发布有这些缺陷呢?
一个人应该如何轻松学习?
在使用 CameraX 与设备的相机交互之前,您需要初始化库。初始化过程是异步的,涉及加载有关设备相机的信息。
您使用 ProcessCameraProvider
与设备的摄像头进行交互。它是一个 Singleton,所以当您第一次获得 if 的实例时,CameraX 会执行它的初始化。
val cameraProviderFuture: ListenableFuture<ProcessCameraProvider> = ProcessCameraProvider.getInstance(context)
获取 ProcessCameraProvider
单例 return 是 Future
因为它可能需要异步初始化库。第一次获取它时,可能需要一些时间(通常不到一秒),但后续调用会立即 return,因为初始化已经执行。
有了ProcessCameraProvider
,您就可以开始与设备的摄像头互动了。您可以使用 CameraSelector
选择要与之交互的相机,它为您要使用的相机包装了一组滤镜。通常,如果您只是想使用主后置或前置摄像头,您会使用 CameraSelector.DEFAULT_BACK_CAMERA
或 CameraSelector.DEFAULT_FRONT_CAMERA
.
现在您已经定义了要使用的相机,您可以构建所需的用例。例如,您想要拍照,因此您将使用 ImageCapture
用例。它允许使用相机拍摄单个捕获帧(通常是高质量帧),并将其作为原始缓冲区提供,或将其存储在文件中。要使用它,您可以根据需要对其进行配置,或者您可以让 CameraX 使用默认配置。
val imageCapture = ImageCapture.Builder().build()
在 CameraX 中,相机的生命周期由 LifecycleOwner
控制,这意味着当 LifecycleOwner
的生命周期开始时,相机打开,当它停止时,相机关闭。因此,您需要选择一个可以控制相机的生命周期。如果您使用 Activity
,您通常希望相机在 Activity
启动时启动,并在它停止时停止,因此您将使用 Activity
实例本身作为LifecycleOwner
,如果您使用的是 Fragment
,您可能希望使用它的视图生命周期 (Fragment.getViewLifecycleOwner()
)。
最后,您需要将拼图的各个部分拼在一起。
processCameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
imageCapture
)
应用程序通常包含一个显示相机预览的取景器,因此您可以使用 Preview
用例,并将其与 ImageCapture
用例绑定。 Preview
用例允许将相机帧流式传输到 Surface
。由于设置 Surface
并在其上正确绘制预览可能很复杂,CameraX 提供了 PreviewView
,一个 View
可以与 Preview
用例一起使用来显示相机预览。您可以查看如何使用它们 here.
// Just like ImageCapture, you can configure the Preview use case if you'd wish.
val preview = Preview.Builder().build()
// Provide PreviewView's Surface to CameraX. The preview will be drawn on it.
val previewView: PreviewView = findViewById(...)
preview.setSurfaceProvider(previewView.surfaceProvider)
// Bind both the Preview and ImageCapture use cases
processCameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
imageCapture,
preview
)
现在要真正拍照,您可以使用 ImageCapture
的 takePicture
方法之一。一个提供捕获图像的 JPEG 原始缓冲区,另一个将其保存在您提供的文件中(如果需要,请确保您具有必要的存储权限)。
imageCapture.takePicture(
ContextCompat.getMainExecutor(context), // Defines where the callbacks are run
object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(imageProxy: ImageProxy) {
val image: Image = imageProxy.image // Do what you want with the image
imageProxy.close() // Make sure to close the image
}
override fun onError(exception: ImageCaptureException) {
// Handle exception
}
}
)
val imageFile = File("somePath/someName.jpg") // You can store the image in the cache for example using `cacheDir.absolutePath` as a path.
val outputFileOptions = ImageCapture.OutputFileOptions
.Builder(imageFile)
.build()
takePicture(
outputFileOptions,
CameraXExecutors.mainThreadExecutor(),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
}
override fun onError(exception: ImageCaptureException) {
}
}
)
Do I really need to write 500 lines of code to simply take a picture? How is this supposed to be considered "simpler than before"?
CameraXBasic 并不像其名称所暗示的那样“基本”x) 它更像是 CameraX 的 3 个用例的完整示例。尽管 CameraFragment
很长,但它很好地解释了事情,因此每个人都更容易理解。
CameraX“比以前更简单”,之前主要指的是Camera2,起码上手有点难度。 CameraX 通过其使用用例的方法提供了对开发人员更友好的 API。它还处理兼容性,这在以前是个大问题。确保您的相机应用在大多数 Android 设备上可靠地工作非常具有挑战性。