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
相关产品推荐
相关产品推荐

