You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AndroidX Camera:如何实现指定区域的捏合缩放

实现AndroidX Camera指定区域的捏合缩放功能

要解决捏合缩放始终以预览中心为基准的问题,核心是将触摸区域的屏幕坐标转换为相机传感器的归一化坐标,再通过CameraX的CameraControl设置缩放中心,而非依赖默认的中心缩放。以下是具体实现步骤:

1. 监听捏合手势与触摸坐标

使用Android原生的ScaleGestureDetector捕获捏合操作,同时记录捏合手势的中心点屏幕坐标:

private lateinit var scaleGestureDetector: ScaleGestureDetector
private var currentZoomRatio = 1f
private lateinit var cameraControl: CameraControl
private lateinit var cameraInfo: CameraInfo

private val scaleListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
    override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
        // 记录当前缩放比例,避免初始缩放跳变
        currentZoomRatio = cameraInfo.zoomState.value?.zoomRatio ?: 1f
        return true
    }

    override fun onScale(detector: ScaleGestureDetector): Boolean {
        // 计算新的缩放比例并限制范围
        val maxZoom = cameraInfo.zoomState.value?.maxZoomRatio ?: 1f
        val newZoom = currentZoomRatio * detector.scaleFactor
        currentZoomRatio = newZoom.coerceIn(1f, maxZoom)

        // 获取捏合中心点的屏幕坐标
        val touchScreenX = detector.focusX
        val touchScreenY = detector.focusY

        // 将屏幕坐标转换为相机传感器归一化坐标
        val previewView = findViewById<PreviewView>(R.id.preview_view)
        val previewMatrix = Matrix()
        previewView.getPreviewMatrix(previewMatrix)
        
        // 反转矩阵,将屏幕坐标映射到预览纹理坐标
        val invertedMatrix = Matrix()
        if (previewMatrix.invert(invertedMatrix)) {
            val touchPoints = floatArrayOf(touchScreenX, touchScreenY)
            invertedMatrix.mapPoints(touchPoints)
            
            // 转换为传感器归一化坐标(0-1范围,(0,0)为传感器左上角)
            val sensorNormX = touchPoints[0] / previewView.width
            val sensorNormY = touchPoints[1] / previewView.height

            // 设置缩放中心与缩放比例
            cameraControl.setZoomCenter(PointF(sensorNormX, sensorNormY))
            cameraControl.setZoomRatio(currentZoomRatio)
        }
        return true
    }
}

2. 绑定触摸监听与Camera实例

在CameraX初始化完成后,绑定预览View的触摸事件,并获取CameraControl和CameraInfo实例:

// 假设已通过CameraProvider绑定相机生命周期
val camera = cameraProvider.bindToLifecycle(
    this, CameraSelector.DEFAULT_BACK_CAMERA, previewUseCase
)
cameraControl = camera.cameraControl
cameraInfo = camera.cameraInfo

// 初始化手势检测器并绑定触摸监听
scaleGestureDetector = ScaleGestureDetector(this, scaleListener)
previewView.setOnTouchListener { _, event ->
    scaleGestureDetector.onTouchEvent(event)
    true
}

3. 适配传感器方向(可选)

部分设备的相机传感器方向与屏幕方向不一致,可能导致缩放中心偏移。可通过cameraInfo.sensorRotationDegrees调整坐标:

// 根据传感器旋转角度调整归一化坐标
val sensorRotation = cameraInfo.sensorRotationDegrees
val adjustedX: Float
val adjustedY: Float
when (sensorRotation) {
    90 -> {
        adjustedX = 1f - sensorNormY
        adjustedY = sensorNormX
    }
    180 -> {
        adjustedX = 1f - sensorNormX
        adjustedY = 1f - sensorNormY
    }
    270 -> {
        adjustedX = sensorNormY
        adjustedY = 1f - sensorNormX
    }
    else -> {
        adjustedX = sensorNormX
        adjustedY = sensorNormY
    }
}
cameraControl.setZoomCenter(PointF(adjustedX, adjustedY))

关键说明

  • 必须使用PreviewView.getPreviewMatrix()获取预览的转换矩阵,该矩阵包含了预览的缩放、旋转、裁剪等变换信息,反转后才能将屏幕坐标正确映射到相机传感器坐标系。
  • 缩放比例需限制在1f到maxZoomRatio之间,避免超出相机硬件支持的范围。
  • 可通过cameraInfo.zoomState的观察者实时更新当前缩放比例,确保手势开始时的初始值准确。

内容的提问来源于stack exchange,提问作者Rahul Mishra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 23:51:09