Android使用CameraX实现相机快速实现对焦和放大缩小
本教程介绍如何使用 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()
}
}
}
}