CameraX 入门食用方法|8月更文挑战

1,345 阅读4分钟

CameraX 已经发布了 1.0正式版 对于涉及到使用摄像头的 App , 能否充分利用摄像头有着很大的区别,为此 对 CameraX 进行了解与认知有一定的必要性.

📑即将学会

用 Jetpack 组件支持库 CameraX 创建相机、拍摄、预览

⭕要求

Google官方声明 CameraX 是 Jetpack组件支持库,帮助我们简化相机相关应用的开发工作。并且提供了一致且易用的 API 接口,适用于大多数 Android 设备,并向后兼容至 Android 5.0(API 级别 21) 因此 创建Android应用的时候mininumSDK 需要选择5.0

前置内容

使用 Intent 进行拍照

在应用中拍照的最简便的方法便是使用MediaStore.ACTION_IMAGE_CAPTURE,传入Intent并启动

image.png 这会启动系统照相机,并为用户提供一套完整的拍照功能 这时,如果用户授予了用户拍照权限,并对拍照图片满意,我们将在onActivityResult中获取图片.默认情况下,拍摄的照片会以缩略图的形式返回,使用键data可以获得缩略图

Bundle extras = data.getExtras();
Bitmap imageBitmap = (Bitmap) extras.get("data");

而要获取完整图片,需要在启动相机的Intent中,在intent的意图中,添加MediaStore.EXTRA_OUTPUT 参数中设置文件的输出URI,该URI会存储完整的照片

image.png 存储后进行完整图片路径后的图片 并进行设置 该存储设置有一定的局限性,请参考官方对拍照的操作 拍照  |  Android 开发者  |  Android Developers (google.cn)) image.png 这是我们日常中会使用到的一些摄像头获取图片的操作. 而在开发过程中,我们对camera2的API使用时还是会有很多脏代码,而CameraX作为Google推出的jetpack组件.简化了对Camera的开发,让我们看看如何使用CameraX

实战

创建项目

image.png

在 App 模块中添加 CameraX 依赖 并同步

image.png

使用 PreviewView 实现 CameraX 预览

1.修改布局文件 布局中添加 PreviewView

在布局文件中进行修改 image.png

Manifest声明相机权限
<uses-permission android:name="android.permission.CAMERA" />
动态申请相机权限
//覆写onRequestPermissionsResult()方法 
override fun onRequestPermissionsResult(
    requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
    if (requestCode == REQUEST_CODE_PERMISSIONS) {
        if (allPermissionsGranted()) {
            startCamera()
        } else {
            Toast.makeText(this,
                "权限未得到用户授予",
                Toast.LENGTH_SHORT).show()
            finish()
        }
    }
2.请求 ProcessCameraProvider

ProcessCameraProvider是一个单例 用于将相机的生命周期绑定到应用程序进程中的任何 生命周期持有者。因为cameraX具有生命周期感应 这可以使我们的应用程序省去打开和关闭相机的任务

val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
3.检查 ProcessCameraProvider 可用性
cameraProviderFuture.addListener(Runnable {}, ContextCompat.getMainExecutor(this))
4.ProcessCameraProvider可用后 选择相机并绑定生命周期和用例
  • 创建 Preview
  • 指定所需的相机
  • 将所选相机和任意用例绑定到生命周期
  • 将 Preview 连接到 PreviewView
 cameraProviderFuture.addListener(Runnable {
            // 1将camera 绑定到 生命周期持有者
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            // 2创建 Preview。
            val preview = Preview.Builder()
                .build()
                .also {
                    //preview 连接 previewView
                    it.setSurfaceProvider(findViewById<PreviewView>(R.id.viewFinder).getSurfaceProvider())
                }

            //3指定所需的相机
            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try {
                // 4绑定前解绑
                cameraProvider.unbindAll()

                // 5绑定用户用例和相机到生命周期
                cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))

效果图

image.png

这是camera的预览功能

cameraX相片拍摄

配置应用拍摄图片
  • 利用 ImageCapture.Builder 构建 imageCapture
  • 绑定用户用例和相机到生命周期 多了一个 imageCapture
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
拍照
  • 获取对 ImageCapture 用例的引用
  • 创建图片存储容纳文件
  • 创建OutputFileOptions指定输出方式 输出路径等内容
val outputFileOptions = ImageCapture.OutputFileOptions.Builder(File(...)).build()
  • 对 imageCapture 对象调用 takePicture()方法。传入刚才构建的 outputOptions 以及保存图片的回调

最终修改代码

cameraProviderFuture.addListener(Runnable {
    // 1将camera 绑定到 生命周期持有者
    val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

    // 2创建 Preview。
    val preview = Preview.Builder()
        .build()
        .also {
            //preview 连接 previewView
            it.setSurfaceProvider(findViewById<PreviewView>(R.id.viewFinder).getSurfaceProvider())
        }
    //todo 新增行
    //图像捕获 构建
    imageCapture = ImageCapture.Builder()
        .build()
    
    //3指定所需的相机 
    val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

    try {
        // 4绑定前解绑
        cameraProvider.unbindAll()
        
        //todo 新增修改行
        // 5绑定用户用例和相机到生命周期
        cameraProvider.bindToLifecycle(
            this, cameraSelector, preview, imageCapture)

    } catch(exc: Exception) {
        Log.e(TAG, "Use case binding failed", exc)
    }

}, ContextCompat.getMainExecutor(this))


private fun takePhoto() {
        // 获取图像捕获用例
        val imageCapture = imageCapture ?: return


        // 创建存储文件对象
        val photoFile = File(
            outputDirectory,
            SimpleDateFormat(FILENAME_FORMAT, Locale.CHINA
            ).format(System.currentTimeMillis()) + ".jpg")

        // 输出条件构建
        val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

        // 拍照 传入输出条件 以及拍照回调
        imageCapture.takePicture(
            outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
                override fun onError(exc: ImageCaptureException) {
                    //异常打印
                    Log.e(TAG, "拍照失败: ${exc.message}", exc)
                }

                override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                    val savedUri = Uri.fromFile(photoFile)
                    val msg = "拍照成功: $savedUri"
                    Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
                    Log.d(TAG, msg)
                }
            })
    }

运行展示

image.png