Android使用CameraX实现相机快速实现对焦和放大缩小

  |   0 评论   |   0 浏览   |   夜雨飘零

本教程介绍如何使用CameraX实现相机点击对焦和放大缩小,单击对焦指定位置,使用双指放大缩小图像。关于如何使用CameraX请看这个教程《Android使用CameraX快速预览和拍照》

下面是页面代码,使用PreviewView预览相机图像,然后使用FocusImageView自定义View来显示对焦框。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.camera.view.PreviewView
        android:id="@+id/viewFinder"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.yeyupiaoling.cameraxapp.FocusImageView
        android:id="@+id/focus_view"
        android:layout_width="75dp"
        android:layout_height="75dp"
        app:focus_fail_id="@drawable/focus_focus_failed"
        app:focus_focusing_id="@drawable/focus_focusing"
        app:focus_success_id="@drawable/focus_focused" />

</FrameLayout>

CameraXPreviewViewTouchListener.kt点监听事件,用于监听屏幕的点击监听动作。

package com.yeyupiaoling.cameraxapp

import android.content.Context
import android.view.GestureDetector
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.ScaleGestureDetector.OnScaleGestureListener
import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener
import android.view.View
import android.view.View.OnTouchListener

/**
 * 自定义CameraX点击事件
 */
class CameraXPreviewViewTouchListener(context: Context?) : OnTouchListener {
    private val mGestureDetector: GestureDetector
    private var mCustomTouchListener: CustomTouchListener? = null
    private val mScaleGestureDetector: ScaleGestureDetector

    override fun onTouch(v: View, event: MotionEvent): Boolean {
        mScaleGestureDetector.onTouchEvent(event)
        if (!mScaleGestureDetector.isInProgress) {
            mGestureDetector.onTouchEvent(event)
        }
        return true
    }

    // 设置监听
    fun setCustomTouchListener(customTouchListener: CustomTouchListener?) {
        mCustomTouchListener = customTouchListener
    }

    // 缩放监听
    var onScaleGestureListener: OnScaleGestureListener = object : SimpleOnScaleGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            val delta = detector.scaleFactor
            if (mCustomTouchListener != null) {
                mCustomTouchListener!!.zoom(delta)
            }
            return true
        }
    }

    // 点击监听
    var onGestureListener: SimpleOnGestureListener = object : SimpleOnGestureListener() {
        override fun onLongPress(e: MotionEvent) {
            if (mCustomTouchListener != null) {
                // 长按
                mCustomTouchListener!!.longPress(e.x, e.y)
            }
        }

        override fun onFling(
            e1: MotionEvent,
            e2: MotionEvent,
            velocityX: Float,
            velocityY: Float
        ): Boolean {
            return true
        }

        override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
            if (mCustomTouchListener != null) {
                // 单击
                mCustomTouchListener!!.click(e.x, e.y)
            }
            return true
        }

        override fun onDoubleTap(e: MotionEvent): Boolean {
            if (mCustomTouchListener != null) {
                // 双击
                mCustomTouchListener!!.doubleClick(e.x, e.y)
            }
            return true
        }
    }

    // 操作接口
    interface CustomTouchListener {
        // 放大缩小
        fun zoom(delta: Float)

        // 点击
        fun click(x: Float, y: Float)

        // 双击
        fun doubleClick(x: Float, y: Float)

        // 长按
        fun longPress(x: Float, y: Float)
    }

    init {
        mGestureDetector = GestureDetector(context, onGestureListener)
        mScaleGestureDetector = ScaleGestureDetector(context, onScaleGestureListener)
    }
}

FocusImageView.kt这个自定义View是用于显示对焦框

package com.yeyupiaoling.cameraxapp

import android.content.Context
import android.graphics.Point
import android.os.Handler
import android.util.AttributeSet
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import android.widget.FrameLayout
import androidx.appcompat.widget.AppCompatImageView

/**
 * 对焦动图显示
 */
class FocusImageView : AppCompatImageView {
    private var mFocusImg = NO_ID
    private var mFocusSucceedImg = NO_ID
    private var mFocusFailedImg = NO_ID
    private val mAnimation: Animation
    private val mHandler: Handler

    constructor(context: Context?) : super(context!!) {
        mAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.focusview_show)
        visibility = GONE
        mHandler = Handler()
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        mAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.focusview_show)
        mHandler = Handler()
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.FocusImageView)
        mFocusImg = typedArray.getResourceId(R.styleable.FocusImageView_focus_focusing_id, NO_ID)
        mFocusSucceedImg =
            typedArray.getResourceId(R.styleable.FocusImageView_focus_success_id, NO_ID)
        mFocusFailedImg = typedArray.getResourceId(R.styleable.FocusImageView_focus_fail_id, NO_ID)
        typedArray.recycle()

        //聚焦图片不能为空
        if (mFocusImg == NO_ID || mFocusSucceedImg == NO_ID || mFocusFailedImg == NO_ID) {
            throw RuntimeException("mFocusImg,mFocusSucceedImg,mFocusFailedImg is null")
        }
    }

    /**
     * 显示对焦图案
     */
    fun startFocus(point: Point) {
        if (mFocusImg == NO_ID || mFocusSucceedImg == NO_ID || mFocusFailedImg == NO_ID) {
            throw RuntimeException("focus image is null")
        }
        //根据触摸的坐标设置聚焦图案的位置
        val params = layoutParams as FrameLayout.LayoutParams
        params.topMargin = point.y - measuredHeight / 2
        params.leftMargin = point.x - measuredWidth / 2
        layoutParams = params
        //设置控件可见,并开始动画
        visibility = VISIBLE
        setImageResource(mFocusImg)
        startAnimation(mAnimation)
    }

    /**
     * 聚焦成功回调
     */
    fun onFocusSuccess() {
        setImageResource(mFocusSucceedImg)
        //移除在startFocus中设置的callback,1秒后隐藏该控件
        mHandler.removeCallbacks(null, null)
        mHandler.postDelayed({ visibility = GONE }, 1000)
    }

    /**
     * 聚焦失败回调
     */
    fun onFocusFailed() {
        setImageResource(mFocusFailedImg)
        //移除在startFocus中设置的callback,1秒后隐藏该控件
        mHandler.removeCallbacks(null, null)
        mHandler.postDelayed({ visibility = GONE }, 1000)
    }

    /**
     * 设置开始聚焦时的图片
     *
     * @param focus
     */
    fun setFocusImg(focus: Int) {
        mFocusImg = focus
    }

    /**
     * 设置聚焦成功显示的图片
     *
     * @param focusSucceed
     */
    fun setFocusSucceedImg(focusSucceed: Int) {
        mFocusSucceedImg = focusSucceed
    }

    companion object {
        private const val NO_ID = -1
    }
}

Activity的java代码:

class MainActivity : AppCompatActivity() {
    private var imageCapture: ImageCapture? = null
    private var mCameraControl: CameraControl? = null
    private var mCameraInfo: CameraInfo? = null
    private var focusView: FocusImageView? = null

    // 使用后摄像头
    private var cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

    private lateinit var cameraExecutor: ExecutorService

    companion object {
        private const val TAG = "MainActivity"
        private const val REQUEST_CODE_PERMISSIONS = 10
        private val REQUIRED_PERMISSIONS =
            arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 对焦框控件
        focusView = findViewById(R.id.focus_view)

        // 请求权限
        if (allPermissionsGranted()) {
            startCamera()
        } else {
            ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
        }

        // 点击拍照
        camera_capture_button.setOnClickListener { takePhoto() }

        cameraExecutor = Executors.newSingleThreadExecutor()
    }


    // 启动相机
    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this@MainActivity)

        cameraProviderFuture.addListener({
            // 绑定生命周期
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            // 设置相机支持预览
            val preview = Preview.Builder().build()
            preview.setSurfaceProvider(viewFinder.surfaceProvider);

            // 设置相机支持拍照
            imageCapture = ImageCapture.Builder()
                // 设置闪光灯
                .setFlashMode(ImageCapture.FLASH_MODE_OFF)
                // 设置照片质量
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
                .build()

            try {
                // 在重新绑定之前取消绑定用例
                cameraProvider.unbindAll()

                // 将用例绑定到摄像机
                val camera: Camera =
                    cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
                // 相机控制,如点击
                mCameraControl = camera.cameraControl
                mCameraInfo = camera.cameraInfo
                initCameraListener()
            } catch (exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }


    // 相机点击等相关操作监听
    private fun initCameraListener() {
        val zoomState: LiveData<ZoomState> = mCameraInfo!!.zoomState
        val cameraXPreviewViewTouchListener = CameraXPreviewViewTouchListener(this)

        cameraXPreviewViewTouchListener.setCustomTouchListener(object :
            CameraXPreviewViewTouchListener.CustomTouchListener {
            // 放大缩小操作
            override fun zoom(delta: Float) {
                Log.d(TAG, "缩放")
                zoomState.value?.let {
                    val currentZoomRatio = it.zoomRatio
                    mCameraControl!!.setZoomRatio(currentZoomRatio * delta)
                }
            }

            // 点击操作
            override fun click(x: Float, y: Float) {
                Log.d(TAG, "单击")
                val factory = viewFinder.meteringPointFactory
                // 设置对焦位置
                val point = factory.createPoint(x, y)
                val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
                    // 3秒内自动调用取消对焦
                    .setAutoCancelDuration(3, TimeUnit.SECONDS)
                    .build()
                // 执行对焦
                focusView!!.startFocus(Point(x.toInt(), y.toInt()))
                val future: ListenableFuture<*> = mCameraControl!!.startFocusAndMetering(action)
                future.addListener({
                    try {
                        // 获取对焦结果
                        val result = future.get() as FocusMeteringResult
                        if (result.isFocusSuccessful) {
                            focusView!!.onFocusSuccess()
                        } else {
                            focusView!!.onFocusFailed()
                        }
                    } catch (e: java.lang.Exception) {
                        Log.e(TAG, e.toString())
                    }
                }, ContextCompat.getMainExecutor(this@MainActivity))
            }

            // 双击操作
            override fun doubleClick(x: Float, y: Float) {
                Log.d(TAG, "双击")
                // 双击放大缩小
                val currentZoomRatio = zoomState.value!!.zoomRatio
                if (currentZoomRatio > zoomState.value!!.minZoomRatio) {
                    mCameraControl!!.setLinearZoom(0f)
                } else {
                    mCameraControl!!.setLinearZoom(0.5f)
                }
            }

            override fun longPress(x: Float, y: Float) {
                Log.d(TAG, "长按")
            }
        })
        // 添加监听事件
        viewFinder.setOnTouchListener(cameraXPreviewViewTouchListener)
    }

    override fun onDestroy() {
        super.onDestroy()
        // 关闭相机
        cameraExecutor.shutdown()
    }

    // 权限申请
    private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
    }

    // 权限申请结果
    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()
            }
        }
    }
}

完整项目源码:https://github.com/yeyupiaoling/CameraXApp


标题:Android使用CameraX实现相机快速实现对焦和放大缩小
作者:夜雨飘零
地址:https://blog.doiduoyi.com/articles/1608712225019.html

评论

发表评论