CameraX的官方介绍
CameraX 是一个 Jetpack 支持库,旨在帮助您简化相机应用的开发工作。它提供一致且易用的 API 接口,适用于大多数 Android 设备,并可向后兼容至 Android 5.0(API 级别 21)。
但是自定义CameraX开发写的代码较多,个人建议使用CameraController——一个Google开发团队封装好的相机管理类。之后的一篇文章是介绍使用CameraControoler和Mlkit库进行开发的拍照、录像和扫描二维码Demo。
本篇文章只是对CameraX得到拍照功能进行简单的封装演示,使用Kotlin+Viewbinding快速开发,欢迎各位指正交流。如果想直接使用CameraController的同学请点击下一篇文章~~
传送门:https://www.jianshu.com/p/e2092b3ab531
想直接下载Demo的同学可以直接点击:
https://github.com/cgztzero/KotlinCameraXDemo
第一步:引入cameraX官方库
// CameraX core library using the camera2 implementation
def camerax_version = "1.1.0-alpha04"
// The following line is optional, as the core library is included indirectly by camera-camera2
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
// If you want to additionally use the CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
// If you want to additionally use the CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha24"
第二步:创建布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:id="@+id/buttonLine"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical"
android:padding="10dp">
<Button
android:id="@+id/takePicture"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="拍照" />
<Button
android:id="@+id/switchCamera"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="切换摄像头" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/autoFlash"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_weight="1"
android:text="flash自动" />
<Button
android:id="@+id/openFlash"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_weight="1"
android:text="flash打开" />
<Button
android:id="@+id/closeFlash"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_weight="1"
android:text="flash关闭" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
很简单的效果
E5ADBD74-90E7-4531-8790-10EF5851D8B6.png
第三步:开启预览+相机设置
1.需要用到的一些字段
protected var mPreview: Preview? = null //预览实例 是一个UseCase
protected lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider> //异步监听对象 监听相机是否准备完成
protected lateinit var cameraExecutor: ExecutorService//相机拍照的线程池
protected lateinit var processCameraProvider: ProcessCameraProvider//当前进程的相机提供者
protected lateinit var cameraSelector: CameraSelector //前置/后置相机选择对象
protected var imageCapture: ImageCapture? = null//拍照实例 是一个UseCase
protected var outputDirectory: String? = null//相片输出路径
protected var imageAnalyzer: ImageAnalysis? = null//相片分析器 可以自定义
protected var lensFacing: Int = -1//记录是前置or后置摄像头
protected var mCamera: Camera? = null//打开相机后获得的相机实例
2.初始化预览,预览初始化后需要和xml中的预览View进行绑定才可以看到画面
protected fun startPreview() {
cameraExecutor = Executors.newSingleThreadExecutor()
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable {
//相机准备完毕后进入回调 初始化一次即可
processCameraProvider = cameraProviderFuture.get()
//实例化预览对象 注意旋转屏幕需要调用多次
bindCameraUseCases()
}, ContextCompat.getMainExecutor(this))//异步 ContextCompat.getMainExecutor相当于通过handler回调在主线程
}
3.bindCameraUseCases()绑定相机实例,这个方法可能被多次调用(比如在手机切换横屏和竖屏的时候)
/**
* 实例化相机
*/
open fun bindCameraUseCases() {
//获得可预览对象
mPreview = Preview.Builder()
.setTargetAspectRatio(aspectRatio())//比例
.setTargetRotation(getRotation())//设置了旋转 就不需要在判断file里的旋转信息了
.build()
//默认选择后置摄像头
if (lensFacing == -1) {
lensFacing = CameraSelector.LENS_FACING_BACK
}
cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
//保险起见 先解绑所有
processCameraProvider.unbindAll()
//初始化拍照实例
initTakePictureCases()
// 所有实例绑定到生命周期 就不需要手动释放相机了
mCamera = processCameraProvider.bindToLifecycle(
this,
cameraSelector,
mPreview,
imageCapture,
imageAnalyzer
)
//预览加载到view上 子类具体实现
onPreviewPrepared(mPreview)
}
4.初始化拍照实例 CamerX库将拍照,录像和解析图形都封装成了UseCase对象
/**
* 初始化拍照实例
*/
private fun initTakePictureCases() {
// ImageCapture
imageCapture = ImageCapture.Builder()
.setFlashMode(ImageCapture.FLASH_MODE_AUTO)//闪光灯设置
.setTargetAspectRatio(aspectRatio())//拍照
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)//低延迟低质量 or 高延迟高质量
.setTargetRotation(getRotation())
.setFlashMode(ImageCapture.FLASH_MODE_AUTO)//闪光灯自动
.build()
// ImageAnalysis
imageAnalyzer = ImageAnalysis.Builder()
//注意源码注释: It is not allowed to set both target aspect ratio and target resolution on the same use case
// .setTargetResolution(Size(1280, 720))
.setTargetRotation(aspectRatio())
.build()
}
/**
* 计算比例 文档里官方写法
*/
private fun aspectRatio(): Int {
val width = getActivityWidth()
val height = getActivityHeight()
val previewRatio = max(width, height).toDouble() / min(width, height)
if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
return AspectRatio.RATIO_4_3
}
return AspectRatio.RATIO_16_9
}
//根据view的旋转角度来给相机设置旋转角度
private fun getRotation(): Int {
return window.decorView.display.rotation
}
5.拍照具体的一些方法调用
/**
* 拍照
*/
private fun takePicture() {
if (outputDirectory.isNullOrEmpty()) {
outputDirectory =
applicationContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES)?.absolutePath + "/Test/"
val file = File(outputDirectory)
if (!file.exists()) {
file.mkdirs()
}
}
val photoFile = createFile()
val metadata = ImageCapture.Metadata().apply {
//水平翻转
isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT
}
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile)
.setMetadata(metadata)
.build()
imageCapture?.takePicture(
outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e("ztzt", "Photo capture failed: ${exc.message}", exc)
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
Log.e("ztzt", "Photo capture succeeded: $savedUri")
}
})
}
//预览要将xml中的preview和preview实例进行绑定
override fun onPreviewPrepared(preview: Preview?) {
super.onPreviewPrepared(preview)
mPreview?.setSurfaceProvider(binding.previewView.surfaceProvider)
}
Demo奉上:
1.想要看自定义拍照的的可以看TakePictureActivity
2.想看全功能的可以看CameraControllerActivity
传送门,
https://github.com/cgztzero/KotlinCameraXDemo
写的不好,希望大家可以多多交流~
网友评论