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

Android自定义ViewGroup旋转后内部视图XY轴移动异常问题求解

问题根源

平移错位的核心原因是坐标系不统一:

  • event.x/event.y是自定义ViewGroup的局部坐标系坐标,当视图设置rotation属性后,局部坐标系会同步旋转,和父容器的全局坐标系(控制v.x/v.y的坐标系)方向不一致
  • 直接把旋转后的局部坐标偏移量加到全局位置属性上,自然出现方向错位。

此外你当前的旋转逻辑也存在增量计算错误的问题:每次双指移动时直接累加计算出的总角度,没有保存双指按下时的初始基准值,会出现旋转跳变的问题。

修复方案

1. 新增必要成员变量

// 平移相关:保存按下时的全局位置
private var initialTranslateX = 0f
private var initialTranslateY = 0f
// 旋转相关:保存双指按下时的基准角度和初始旋转值
private var initialRotation = 0f
private var prevRotationDegree = 0f

2. 新增坐标转换工具方法

把局部坐标系的偏移量转换为父容器全局坐标系的偏移量:

private fun getGlobalOffset(localDx: Float, localDy: Float): Pair<Float, Float> {
    val rad = Math.toRadians(rotation.toDouble())
    val cos = cos(rad).toFloat()
    val sin = sin(rad).toFloat()
    // 通过旋转变换逆运算得到全局偏移
    val globalDx = localDx * cos + localDy * sin
    val globalDy = -localDx * sin + localDy * cos
    return Pair(globalDx, globalDy)
}

3. 修正MotionEvent处理逻辑

替换原有的motionEventHandler实现:

private val motionEventHandler: (view: View, event: MotionEvent) -> Boolean = { v, event ->
    scaleDetector.onTouchEvent(event)
    val pointerCount = event.pointerCount

    when (event.actionMasked) {
        MotionEvent.ACTION_DOWN -> {
            if (drawFrame) {
                initialX = event.x
                initialY = event.y
                // 保存按下时的全局位置
                initialTranslateX = v.x
                initialTranslateY = v.y
            }
            performClick()
        }
        MotionEvent.ACTION_POINTER_DOWN -> {
            // 双指按下时保存旋转基准值
            if (pointerCount == 2 && drawFrame) {
                val firstX = event.getX(0)
                val firstY = event.getY(0)
                val secondX = event.getX(1)
                val secondY = event.getY(1)
                val deltaX = firstX - secondX
                val deltaY = firstY - secondY
                prevRotationDegree = Math.toDegrees(atan2(deltaY, deltaX).toDouble()).toFloat()
                initialRotation = rotation
            }
        }
        MotionEvent.ACTION_MOVE -> {
            if (drawFrame) {
                // 平移逻辑
                if (pointerCount == 1) {
                    val localDx = event.x - initialX
                    val localDy = event.y - initialY
                    val (globalDx, globalDy) = getGlobalOffset(localDx, localDy)
                    v.x = initialTranslateX + globalDx
                    v.y = initialTranslateY + globalDy

                    // 修正边界限制逻辑(适配旋转后的视图尺寸)
                    (parent as FrameLayout).let { parent ->
                        val parentWidth = parent.width
                        val parentHeight = parent.height
                        // 计算旋转后的视图实际包围盒
                        val rotatedRect = RectF()
                        matrix.mapRect(rotatedRect, RectF(0f, 0f, width.toFloat(), height.toFloat()))
                        val rotatedWidth = rotatedRect.width()
                        val rotatedHeight = rotatedRect.height()

                        v.x = v.x.coerceAtLeast(parent.x).coerceAtMost(parentWidth - rotatedWidth)
                        v.y = v.y.coerceAtLeast(parent.y).coerceAtMost(parentHeight - rotatedHeight)
                    }
                }

                // 旋转逻辑
                if (pointerCount == 2) {
                    val firstX = event.getX(0)
                    val firstY = event.getY(0)
                    val secondX = event.getX(1)
                    val secondY = event.getY(1)
                    val deltaX = firstX - secondX
                    val deltaY = firstY - secondY
                    val currentDegree = Math.toDegrees(atan2(deltaY, deltaX).toDouble()).toFloat()
                    // 仅累加角度增量,避免跳变
                    rotation = initialRotation + (currentDegree - prevRotationDegree)
                }
            }
        }
    }
    true
}

可选优化

如果需要更流畅的交互体验,还可以新增ScaleGestureDetector的坐标适配,以及缩放时同步调整边界限制逻辑,避免缩放后超出父容器范围。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 20:12:03