Jetpack-CameraX的使用:预览、前后摄像头切换、拍照

3,744 阅读4分钟

前言

官方介绍:CameraX 是 Jetpack 的新增库。利用该库,可以更轻松地向应用添加相机功能。该库提供了很多兼容性修复程序和权宜解决方法,有助于在众多设备上打造一致的开发者体验。

一相机功能的开发使用向来是个麻烦事,涉及到各种设备的适配问题,需要在代码中添加一堆的设备专属代码。官方也可能意识到这个问题,camerax正是为了简化开发工作而推出来的库。声称:借助 CameraX,开发者只需两行代码就能实现与预安装的相机应用相同的相机体验和功能。

这里先上效果图:

微信图片_20211130115055.jpg

微信图片_20211130115059.jpg

使用

基础配置

  1. 在项目的build.gradle添加代码库:google()
allprojects {
    repositories {
        ...
        google()
    }
}
  1. 配置module中的build.gradle
android {
    ...
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    // For Kotlin projects
    kotlinOptions {
        jvmTarget = "1.8"
    }
}
  1. 添加库引用
def camera_version = "1.1.0-alpha11"
// The following line is optional, as the core library is included indirectly by camera-camera2
implementation("androidx.camera:camera-core:${camera_version}")
implementation("androidx.camera:camera-camera2:${camera_version}")
// If you want to additionally use the CameraX Lifecycle library
implementation("androidx.camera:camera-lifecycle:${camera_version}")
// If you want to additionally use the CameraX View class
implementation("androidx.camera:camera-view:1.0.0-alpha31")
// If you want to additionally use the CameraX Extensions library
implementation("androidx.camera:camera-extensions:1.0.0-alpha31")

通过上述配置,即可正常使用camerax库的功能。下图是当前的一些版本信息: camerax_version.png

不过这里有个注意事项,官方的使用介绍文档中配置的版本是:1.0.2稳定版,当android中的compileSdk配置成31时,在运行时会报错:

NoSuchError.png 这里有两种方式可以处理:

  • 将我们的compileSdk版本配置到30或以下
  • 使用1.1.0-alpha11版本

实现预览效果

  1. 首先,拍照需要申请权限,因此需要先申请拍照权限~当然,假如要将图片保存到文件夹中,可能还需要额外的WRITE_EXTERNAL_STORAGE权限,当然这不在我们的详细讨论范围
private fun checkPermission() {
    if (ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.CAMERA
        ) != PackageManager.PERMISSION_GRANTED
    ) {
        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), REQ_CAMERA)
    } else {
        initCamera()
    }
}
  1. 自定义Application,配置CameraXConfig.Provider
class CameraXApplication : Application(), CameraXConfig.Provider {
    override fun getCameraXConfig(): CameraXConfig {
        return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
            .setMinimumLoggingLevel(Log.ERROR)
            .build();
    }
}
  1. 添加PreviewView到布局中,用于拍照预览显示
<androidx.camera.view.PreviewView
    android:id="@+id/preview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
  1. 创建Preview,并将PreviewView绑定到Preview
previewView = findViewById(R.id.preview)
preview = Preview.Builder().build()
preview.setSurfaceProvider(previewView.surfaceProvider)
  1. 获取CameraProvider
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable {
    cameraProvider = cameraProviderFuture.get()
    setupCamera()
}, ContextCompat.getMainExecutor(this))
  1. 通过CameraSeletor选择相机,并通过cameraprovider绑定到生命周期
cameraProvider?.let {
    cameraProvider.unbindAll()
    val cameraSelector =
        CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_BACK)
            .build()
    cameraProvider.bindToLifecycle(
        this as LifecycleOwner,
        cameraSelector,
        imageCapture,
        imageAnalysis,
        preview
    )
}

至此,我们已经可以正常的预览了~看起来流程不少,但其实整个流程是通用的

拍照

通过ImageCapture提供的takePicture接口,我们即可实现拍照功能~

  1. 获取ImageCapture
imageCapture =
    ImageCapture.Builder().setTargetRotation(previewView.display.rotation).build()
  1. 将capture绑定到生命周期
cameraProvider.bindToLifecycle(
    this as LifecycleOwner,
    cameraSelector,
    imageCapture,
    imageAnalysis,
    preview
)
  1. 调用takePicture进行拍照 在布局中增加拍照按钮,通过按钮触发拍照
<Button
    android:id="@+id/take_pic"
    android:layout_width="150dp"
    android:layout_height="45dp"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:layout_marginBottom="10dp"
    android:text="拍照" />
    
imageCapture?.takePicture(
    outputFileOption,
    ContextCompat.getMainExecutor(this as Context),
    object : ImageCapture.OnImageSavedCallback {
        override fun onError(error: ImageCaptureException) {
            Toast.makeText(this@MainActivity, "拍照出错啦", Toast.LENGTH_SHORT).show()
        }

        override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
            Toast.makeText(
                this@MainActivity,
                outputFileResults.savedUri.toString(),
                Toast.LENGTH_SHORT
            ).show()
        }
    })

OnImageSavedCallback回调中包含两个函数,拍照成功:onImageSaved,出现异常:onError。当成功后,可通过参数outputFileResults获取保存图片的uri

前后摄像头的切换

在前边的章节有说到,通过cameraSelector选择相机并绑定到生命周期。查看下cameraSelector的requireLensFacing函数

Requires a camera with the specified lens facing.
Valid values for lens facing are LENS_FACING_FRONT and LENS_FACING_BACK.
If lens facing is already set, this will add extra requirement for lens facing instead of replacing the previous setting.
@NonNull
public Builder requireLensFacing(@LensFacing int lensFacing) {
    mCameraFilterSet.add(new LensFacingCameraFilter(lensFacing));
    return this;
}

接下来在布局增加按钮用于切换前后摄像头

<Button
    android:id="@+id/toggle_camera"
    android:layout_width="45dp"
    android:layout_height="45dp"
    android:layout_alignParentRight="true"
    android:layout_alignParentBottom="true"
    android:layout_marginRight="10dp"
    android:layout_marginBottom="10dp"
    android:background="@android:drawable/ic_menu_camera" />
    
toggleCamera.setOnClickListener {
    isBackCamera = !isBackCamera
    setupCamera()
}

private fun setupCamera() {
    cameraProvider?.let {
        cameraProvider.unbindAll()
        val cameraSelector =
            CameraSelector.Builder()
                .requireLensFacing(if (isBackCamera) CameraSelector.LENS_FACING_BACK else CameraSelector.LENS_FACING_FRONT)
                .build()
        cameraProvider.bindToLifecycle(
            this as LifecycleOwner,
            cameraSelector,
            imageCapture,
            imageAnalysis,
            preview
        )
    }
}

通过重新设置cameraSelector,即可切换预览前后摄

其他参数设置:前摄图片翻转问题

当切换到前摄像头并进行拍照,查看图片会发现,图片跟预览的效果刚好是镜像翻转的~怎么解决这个问题呢?其实,在我们调用ImageCapture的takePicture时,需要传递一个ImageCapture.OutputFileOptions参数,我们可以通过配置该参数,让生成的图片进行旋转。

OutputFileOptions里包含一个Metadata的参数,看下api文档

@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public static final class Metadata {
    /**
     * Indicates a left-right mirroring (reflection).
     *
     * <p>The reflection is meant to be applied to the upright image (after rotation to the
     * target orientation). When saving the image to file, it is combined with the rotation
     * degrees, to generate the corresponding EXIF orientation value.
     */
    private boolean mIsReversedHorizontal;

    /**
     * Whether the mIsReversedHorizontal has been set by the app explicitly.
     */
    private boolean mIsReversedHorizontalSet = false;

    /**
     * Indicates an upside down mirroring, equivalent to a horizontal mirroring (reflection)
     * followed by a 180 degree rotation.
     *
     * <p>The reflection is meant to be applied to the upright image (after rotation to the
     * target orientation). When saving the image to file, it is combined with the rotation
     * degrees, to generate the corresponding EXIF orientation value.
     */
    private boolean mIsReversedVertical;

mIsReversedHorizontal(左右镜像翻转)参数不正好是我们需要的吗?因此我们可以判断当前拍照是否是前摄像头,然后调整该参数

val metadata = ImageCapture.Metadata()
metadata.isReversedHorizontal = !isBackCamera
val outputFileOption =
    ImageCapture.OutputFileOptions.Builder(File(savePath)).setMetadata(metadata).build()
imageCapture?.takePicture(
    outputFileOption,
    ContextCompat.getMainExecutor(this as Context),
    object : ImageCapture.OnImageSavedCallback {
        override fun onError(error: ImageCaptureException) {
        }

        override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
        }
    })

查看拍照的效果

这里为了方便,直接引用glide进行图片加载显示

implementation 'com.github.bumptech.glide:glide:4.12.0'

val path: String? = intent.getStringExtra(PREVIEW_PATH)
preview = findViewById(R.id.preview)
path?.let {
    val uri = Uri.parse(path)
    Glide.with(this)
        .load(uri)
        .apply(RequestOptions.fitCenterTransform())
        .into(preview)
}

总结

上边只是简单介绍了camerax库的基本使用~这个库提供的功能远不止于次,例如

  • 图片分析:ImageAnalysis
  • 图片旋转纠正
  • Extensions API:焦外成像脸部照片修复HDR(高动态范围) 等功能 当然,通过不断摸索,我们可以通过camerax库实现一个强大的拍照app,后续有时间再慢慢补充。

最后附上demo地址:gitee-demo