前语

上一篇简略介绍了自己运用Camera+Surfaceview自界说相机的情况,想了解的可直接前往/post/718533… ,那这一篇持续介绍怎么运用Jetpack的CameraX来自界说相机。

先来说说自己运用下来的相比Camera的一个感触吧:

  • 运用简略,无需过多重视相机的装备(这儿就能够解决相机预览变形和旋转的问题)
  • 生命周期绑定,这儿能够省去翻开关闭相机和对相机进行生命周期办理的工作

运用CameraX自界说相机也能够简略分为以下几步:

  • 权限装备
  • 布局装备
  • 预览设置
  • 摄影设置

第一步 权限装备

运用CameraX所需求的权限和Camera类似,主要是以下几种:

<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
  android:maxSdkVersion="28" />

android.hardware.camera.any的装备是声明CameraX能够运用设备上的任何一个摄像头,不论是前置仍是后置,和Camera获取一切相机来进行指定类似。因为上述权限涉及到Android6.0及以上的动态权限请求,我个人习惯直接运用Google的easyPermission库进行动态权限请求。获取许到相应的权限才能持续后续的相机定制化运用哟~

第二步 布局装备

既然是自界说相机,那么布局就依据自己的实际情况布局即可。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
​
  <androidx.camera.view.PreviewView
    android:id="@+id/view_preview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
​
  <LinearLayout
    android:id="@+id/view_bottom_operate"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:background="#FFF"
    android:gravity="center"
    android:orientation="horizontal"
    android:paddingStart="10dp"
    android:paddingEnd="10dp"
    android:visibility="visible"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent">
​
    <androidx.appcompat.widget.AppCompatImageView
      android:id="@+id/btn_cancle"
      android:layout_width="0dp"
      android:layout_height="50dp"
      android:layout_weight="1"
      android:gravity="center"
      android:src="@mipmap/icon_cancle"
      android:textColor="#000"
      android:textSize="18sp" />
​
    <androidx.appcompat.widget.AppCompatImageView
      android:id="@+id/btn_take_picture"
      android:layout_width="0dp"
      android:layout_height="50dp"
      android:layout_weight="1"
      android:gravity="center"
      android:src="@mipmap/icon_take_picture"
      android:textColor="#000"
      android:textSize="18sp" />
​
    <androidx.appcompat.widget.AppCompatImageView
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:text=""
      android:textColor="#000"
      android:textSize="18sp" />
  </LinearLayout>
​
</androidx.constraintlayout.widget.ConstraintLayout>

第三步 实现预览

实现预览能够分为以下几步:

  • 获取相机进程提供者ProcessCameraProvider
  • 为相机进程提供者添加监听
  • 创立预览窗口
  • 指定预览相机
  • 绑定相机进程提供者到持有者生命周期
  • 添加执行器

具体的每一步对应的代码能够直接参考代码及注释,这儿就不在过多赘述。

// 获取相机进程提供者实例
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
​
// 为相机进程提供者添加监听
cameraProviderFuture.addListener({
​
  // 获取具体的相机进程提供者
  val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
  // 创立相机预览窗口
  val preview = Preview.Builder()
     .build()
     .also {
      // 这儿通过咱们布局中的Preview进行预览
      it.setSurfaceProvider(binding.viewPreview.surfaceProvider)
     }
​
  // 获取用于摄影的实例
  imageCapture = ImageCapture.Builder()
     .build()
​
  // 指定用于预览的相机,默认为后置相机,假如需求前置相机预览请运用CameraSelector.DEFAULT_FRONT_CAMERA
  val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
  try {
    // 绑定提供者之前先进行解绑,不能重复绑定
    cameraProvider.unbindAll()
​
    // 绑定提供者,将相机的生命周期进行绑定,因为camerax具有生命周期感知力,所以消除翻开和关闭相机的任务
    cameraProvider.bindToLifecycle(
      this, cameraSelector, preview, imageCapture
     )
​
   } catch (e: Exception) {
    Log.e(TAG, "相机绑定异常 ${e.message}")
   }
}, ContextCompat.getMainExecutor(this))

第四步 摄影

在调用摄影前咱们需求指定一个摄影后的文件存放位置,然后再进行摄影,摄影后咱们能够依据事先指定的路径获取到咱们摄影的文件。因为Android10之后官方逐渐的约束咱们运用File的方法去办理文件,因而这儿咱们运用MediaStore进行。因而摄影相同以下几步:

  • 界说相片称号
  • 运用MediaStore操作相片
  • 装备摄影输出参数
  • 开端摄影

1、界说相片称号

这儿为了相片不重复或许不被掩盖,主张运用时刻戳界说文件称号,这样能有效的防止文件被掩盖。

SimpleDateFormat(FILENAME_FORMAT, Locale.CHINA).format(System.currentTimeMillis())

2、运用MediaStore操作相片

val contentValues = ContentValues().apply {
  put(MediaStore.MediaColumns.DISPLAY_NAME, name)
  put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
  if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
    put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
   }
}

3、装备摄影输出参数

// 指定输出参数
val outputOptions = ImageCapture.OutputFileOptions
   .Builder(
    contentResolver,
    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
    contentValues
   )
   .build()

4、开端摄影

// 开端摄影相片
imageCapture.takePicture(
  outputOptions,
  ContextCompat.getMainExecutor(this),
  object : ImageCapture.OnImageSavedCallback {
    override fun onError(exc: ImageCaptureException) {
       ......
     }
​
    override fun onImageSaved(output: ImageCapture.OutputFileResults) {
      ......
     }
   }
)

完整摄影代码如下:

/**
 * 摄影
 */
private fun takePhoto() {
  // 校验是否有可用的相机摄影器
  val imageCapture = imageCapture ?: return
  // 界说摄影相片称号
  val name = SimpleDateFormat(FILENAME_FORMAT, Locale.CHINA)
     .format(System.currentTimeMillis())
​
  // 运用MediaStore操作相片文件
  val contentValues = ContentValues().apply {
    put(MediaStore.MediaColumns.DISPLAY_NAME, name)
    put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
      put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
     }
   }
​
  // 指定输出参数
  val outputOptions = ImageCapture.OutputFileOptions
     .Builder(
      contentResolver,
      MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
      contentValues
     )
     .build()
​
  // 开端摄影相片
  imageCapture.takePicture(
    outputOptions,
    ContextCompat.getMainExecutor(this),
    object : ImageCapture.OnImageSavedCallback {
      override fun onError(exc: ImageCaptureException) {
        // 摄影失利
        val msg = "Photo capture failed: ${exc.message}"
        Log.e(TAG, msg, exc)
        Toast.makeText(this@CameraActivity, msg, Toast.LENGTH_SHORT).show()
       }
​
      override fun onImageSaved(output: ImageCapture.OutputFileResults) {
        // 摄影成功,saveUri便是图片的uri地址
        val msg = "Photo capture succeeded: ${output.savedUri}"
        val intent = Intent()
        val bundle = Bundle()
        bundle.putString("picture_uri", output.savedUri.toString())
        intent.putExtra("result", bundle)
        this@CameraActivity.setResult(Activity.RESULT_OK, intent)
        this@CameraActivity.finish()
        Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
        Log.d(TAG, msg)
       }
     }
   )
}

注:通过uri获取到对应相片后,假如要做显现或许上传都相关操作,个人主张进行适当的紧缩处理,毕竟现在的大多数手机摄影出来的相片质量都比较大,操作起来内存毁掉或许网络、时刻耗费都比较大。

最终

demo作用同Camera自界说相机共同,

Android自定义相机—Jetpack CameraX篇

至此,关于怎么运用Jectpack CameraX自界说相机就介绍完了,关于更多定制化的需求能够依据自己的实际情况进行扩展,如需项目相关Demo能够前往个人码云库房获取: