前言

  google推出Camera后,发现Camera功能简单,难以满足需求调用Camera各种效果,所以又推出了Camera2. Camera2功能强大但是使用十分麻烦,回调与冗余代码太多,而且特别容易在释放Camera上犯错导致activty的内存泄露. 所以google推出了更简单易用,但是功能也强大的CameraX.

  因为CameraX的简单可以帮助我们高效率开发,所以也是有学习的必要性.(Camera2了解就行,没必要死磕浪费太多时间),CameraX有以下优势:

  1. CameraX与Liftcycle结合,与Activity或者Fragment的生命周期捆绑,不要考虑摄像头的释放问题,减少了代码的复杂度.
  2. CameraX兼容至 Android L (API 21)
  3. 依然支持Camera2的丰富摄像头功能
添加依赖
    def camerax_version = "1.0.0-beta03"
    // 使用camera2实现的CameraX核心库
    implementation "androidx.camera:camera-camera2:$camerax_version"
    // CameraX 生命周期库
    implementation "androidx.camera:camera-lifecycle:$camerax_version"
    // CameraX 视图类
    implementation "androidx.camera:camera-view:1.0.0-alpha10"

 

获取权限

跟以前一样,需要动态授权一些必要权限

 <!-- 相机相关 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
预览摄像头画面

从最简单的预览摄像头图像开始,我们逐步了解使用方式,代码如下:

class CameraXActivity : AppCompatActivity() {
    private val TAG = CameraXActivity::class.java.simpleName
private lateinit var mPreviewView: PreviewView
override fun onCreate(savedInstanceState: Bundle
?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera_x2) mPreviewView = findViewById(R.id.previewView) startCameraPreview() } /** * 开始相机预览 */ private fun startCameraPreview() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { //用于将相机的生命周期绑定到生命周期所有者 val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() //创建预览 val preview = Preview.Builder().build() //选择后置摄像头 val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() try { //在重新绑定之前取消绑定 cameraProvider.unbindAll() //将生命周期,选择摄像头,预览,绑定到相机 val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview) //设置预览的View preview.setSurfaceProvider(mPreviewView.createSurfaceProvider(camera.cameraInfo)) } catch (exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } }, ContextCompat.getMainExecutor(this)) } }

特别简单就完成了,而且无需考虑摄像头的释放

实现拍照
class CameraXActivity : AppCompatActivity() {
    private val TAG = CameraXActivity::class.java.simpleName
    private lateinit var mImageCapture: ImageCapture
    private lateinit var mImageAnalysis: ImageAnalysis
    private lateinit var mPreviewView: PreviewView
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_camera_x2)
        mPreviewView = findViewById(R.id.previewView)
        startCameraPreview()
        takePhoto.setOnClickListener {
            //点击后拍照
            takePhoto()
        }
    }

    /**
     * 开始相机预览
     */
    private fun startCameraPreview() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener(Runnable {
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
            val preview = Preview.Builder().build()
            //创建图像捕捉
            mImageCapture = ImageCapture.Builder().build()
            val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
            try {
                cameraProvider.unbindAll()
                //请注意,这里新增了一个ImageCapture
                val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, mImageCapture)
                preview.setSurfaceProvider(mPreviewView.createSurfaceProvider(camera.cameraInfo))
            } catch (exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }
        }, ContextCompat.getMainExecutor(this))
    }

    /**
     * 拍照
     */
    private fun takePhoto() {
        //图像的保存路径与名称
        val photoFile = File(applicationContext.externalCacheDir?.path
                , SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(System.currentTimeMillis()) + ".jpg")

        // 创建图像文件输出选项
        val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

        //拍照,并且注册拍照后的结果监听
        mImageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
            override fun onError(exc: ImageCaptureException) {
                Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
            }

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

拍照图像旋转

代码的其他部分与上面的示例代码一致, 我们只需要关注ImageCapture的创建与添加setTargetRotation

    private fun createImageCapture():ImageCapture{
        //创建图像捕捉
        mImageCapture = ImageCapture.Builder()
                .setTargetRotation(Surface.ROTATION_90)//设置旋转角度,并且只能有4个旋转方向属性ROTATION_0/ROTATION_90/ROTATION_180/ROTATION_270
                .build()
        return mImageCapture
    }

设置执行IO线程

    private fun createImageCapture():ImageCapture{
        //创建图像捕捉
        mImageCapture = ImageCapture.Builder()
                .setIoExecutor(Executors.newSingleThreadExecutor())//设置执行IO线程
                .build()
        return mImageCapture
    }

设置捕捉模式

    private fun createImageCapture():ImageCapture{
        //创建图像捕捉
        mImageCapture = ImageCapture.Builder()
                //CAPTURE_MODE_MAXIMIZE_QUALITY 高画质
                //CAPTURE_MODE_MINIMIZE_LATENCY 低延迟
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
                .build()
        return mImageCapture
    }

设置闪光灯

    private fun createImageCapture():ImageCapture{
        //创建图像捕捉
        mImageCapture = ImageCapture.Builder()
                //FLASH_MODE_ON 闪光灯开启
                //FLASH_MODE_OFF 闪光灯关闭
                //FLASH_MODE_AUTO 闪光灯自动
                .setFlashMode(ImageCapture.FLASH_MODE_ON)
                .build()
        return mImageCapture
    }

设置宽高比

    private fun createImageCapture():ImageCapture{
        mImageCapture = ImageCapture.Builder()
                //RATIO_4_3   4比3
                //RATIO_16_9  16比9
                .setTargetAspectRatio(AspectRatio.RATIO_16_9)
                .build()
        return mImageCapture
    }

设置分辨率

下面还帖了一些注释,这注释的意思是,你可以随便设置分辨率大小,但是真正的分辨率并不一定是你设置的数值,而是在摄像头里获取的分辨率列表中去取最近似值.

为什么会有这种说明? 我这里给没有接触过摄像头开发的朋友说明一下:

手机的摄像头的分辨率并不是可以随便设置的,这需要取决于你开发的设备的摄像头驱动的分辨率列表. 在以往开发Camera1和Camera2的时候我们需要自己获取这份列表,从中选择我们需要的分辨率. 在使用CameraX的时候他们帮我们简化了这个筛选过程,你只需要设置目标分辨率,代码会自动选择近似分辨率

    private fun createImageCapture(): ImageCapture {
        mImageCapture = ImageCapture.Builder()
                /*
                目标分辨率尝试建立图像分辨率的最小界限。实际图像分辨率将是尺寸上最接近的可用分辨率,该分辨率不小于由相机实现确定的目标分辨率。
                但是,如果不存在等于或大于目标分辨率的分辨率,则将选择最接近的小于目标分辨率的可用分辨率。
                与提供的 {@link Size} 具有相同纵横比的分辨率将在不同纵横比的分辨率之前优先考虑。
                 */
                .setTargetResolution(Size(1280, 720))
                .build()
        return mImageCapture
    }

 

 

 

 

 

 

End