视图在旋转后无法在 x 和 y 轴上正确移动

View does not move correctly in x and y axis after it's been rotated

我目前正在一个视图组中编写代码,该视图组有一个框架布局作为子视图,其中有一个视图。这个视图组负责通过在其中实现 MotionEvent 来旋转、缩放、移动视图。到目前为止,我已经能够在其中实现旋转、缩放、移动,它们工作得很好,直到我旋转整个视图组,之后它似乎没有按预期移动(顺便说一句,缩放没问题)。

我想问题是在我将视图旋转 180 度(或什至一点)后,x 和 y 位置有点互换并且它不再起作用(直到它旋转回其原始位置).提前谢谢你。

if rotation is not applied image

if rotation is applied image

运动事件代码:

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

    val pointerCount = event.pointerCount

    when (event.actionMasked) {
        MotionEvent.ACTION_DOWN -> {
            // if view is in editing state
            if (drawFrame) {
                // Save the initial x and y of that touched point
                initialX = event.x
                initialY = event.y
            }
            performClick()
        }
        MotionEvent.ACTION_MOVE -> {
            // If view is in editing state (got clicked)
            if (drawFrame) {

                /* Moving the view by touch */

                // and if there is only 1 pointer on the screen
                if (pointerCount == 1) {

                    // Move the view
                    v.x += event.x - initialX
                    v.y += event.y - initialY

                    // Don't let the view go beyond the phone's display and limit it's x and y
                    (parent as FrameLayout).let { parent ->
                        val parentWidth = parent.width
                        val parentHeight = parent.height

                        if ((v.x + v.width) >= parentWidth) v.x =
                            (parentWidth - v.width).toFloat()

                        if ((v.y + v.height) >= parentHeight) v.y =
                            (parentHeight - v.height).toFloat()

                        if (v.x <= parent.x) v.x = parent.x
                        if (v.y <= parent.y) v.y = parent.y
                    }
                }

                /* Rotating the view by touch */
                // If there are total of two pointer on the screen
                if (pointerCount == 2) {
                    rotatedDegree =
                        event.run { /* <----- I think problem is in that code block */
                            // Get the first pointer x and y
                            val (firstX, firstY) = getPointerInfoAt(getPointerId(0))
                            // Get the second pointer x and y 
                            val (secondX, secondY) = getPointerInfoAt(getPointerId(1))

                            // Calculate the difference between those points
                            val deltaX = firstX - secondX
                            val deltaY = secondY - firstY

                            // Get the total degree that view got rotated 
                            val totalDegreeOfRotation =
                                Math.toDegrees(atan2(deltaX, deltaY).toDouble()).toFloat()

                            Log.i(
                                "MotionEvent",
                                "Total degree of rotation is $totalDegreeOfRotation  " +
                                        "first x : "
                            )
                            totalDegreeOfRotation
                        }
                    // Rotate the ViewGroup 
                    rotation += rotatedDegree
                }
            }
        }
    }
    true
}

缩放代码:

  private val scaleListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {

    override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
        if (!isChildMeasured) {
            initialScaleHeight = child.height
            initialScaleWidth = child.width
            isChildMeasured = !isChildMeasured
        }

        Log.i(
            "SCALE",
            "onScaleBegin: InitialScaleWidth $initialScaleWidth || InitialScaleHeigh $initialScaleHeight"
        )
        return true
    }

    override fun onScale(detector: ScaleGestureDetector?): Boolean {
        scaleFactor *= detector!!.scaleFactor
        scaleFactor = max(0.1f, min(scaleFactor, 2.0f))

        var childTextSize = child.textSize
        childTextSize *= scaleFactor

        if (childTextSize < 18f) childTextSize = 18f
        if (childTextSize > 85f) childTextSize = 85f

        child.textSize = childTextSize
        // In views we should only change the property that determines the view size, not the actual view size

        requestLayout()

        return true
    }
}

EditableView.kt(全部代码):

class EditableView(context: Context, attr: AttributeSet?) : ViewGroup(context, attr) {

constructor(context: Context) : this(context, null)

private var drawFrame: Boolean = true

private var scaleFactor = 1f

private var initialScaleWidth = 0
private var initialScaleHeight = 0

private var rotatedDegree = 0f

private val child: TextView
    get() =
        mainViewHolder.children.first() as TextView


private var isChildMeasured: Boolean = false

private val scaleListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {

    override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
        if (!isChildMeasured) {
            initialScaleHeight = child.height
            initialScaleWidth = child.width
            isChildMeasured = !isChildMeasured
        }

        Log.i(
            "SCALE",
            "onScaleBegin: InitialScaleWidth $initialScaleWidth || InitialScaleHeigh $initialScaleHeight"
        )
        return true
    }

    override fun onScale(detector: ScaleGestureDetector?): Boolean {
        scaleFactor *= detector!!.scaleFactor
        scaleFactor = max(0.1f, min(scaleFactor, 2.0f))

        var childTextSize = child.textSize
        childTextSize *= scaleFactor

        if (childTextSize < 18f) childTextSize = 18f
        if (childTextSize > 85f) childTextSize = 85f

        child.textSize = childTextSize
        // In views we should only change the property that determines the view size, not the actual view size

        requestLayout()

        return true
    }
}

private val scaleDetector = ScaleGestureDetector(context, scaleListener)

private var initialX = 0f
private var initialY = 0f

private val motionEventHandler: (view: View, event: MotionEvent) -> Boolean = { v, event ->
    // For scaling
    scaleDetector.onTouchEvent(event)
    val pointerCount = event.pointerCount
    when (event.actionMasked) {
        MotionEvent.ACTION_DOWN -> {
            // if view is in editing state
            if (drawFrame) {
                // Save the initial x and y of that touched point
                initialX = event.x
                initialY = event.y
            }
            performClick()
        }
        MotionEvent.ACTION_MOVE -> {
            // If view is in editing state (got clicked)
            if (drawFrame) {

                /* Moving the view by touch */

                // and if there is only 1 pointer on the screen
                if (pointerCount == 1) {

                    // Move the view
                    v.x += event.x - initialX
                    v.y += event.y - initialY

                    // Don't let the view go beyond the phone's display and limit it's x and y
                    (parent as FrameLayout).let { parent ->
                        val parentWidth = parent.width
                        val parentHeight = parent.height

                        if ((v.x + v.width) >= parentWidth) v.x =
                            (parentWidth - v.width).toFloat()

                        if ((v.y + v.height) >= parentHeight) v.y =
                            (parentHeight - v.height).toFloat()

                        if (v.x <= parent.x) v.x = parent.x
                        if (v.y <= parent.y) v.y = parent.y
                    }
                }

                /* Rotating the view by touch */
                // If there are total of two pointer on the screen
                if (pointerCount == 2) {
                    rotatedDegree =
                        event.run { /* <----- I think problem is in that code block */
                            // Get the first pointer x and y
                            val (firstX, firstY) = getPointerInfoAt(getPointerId(0))
                            // Get the second pointer x and y
                            val (secondX, secondY) = getPointerInfoAt(getPointerId(1))

                            // Calculate the difference between those points
                            val deltaX = firstX - secondX
                            val deltaY = secondY - firstY

                            // Get the total degree that view got rotated
                            val totalDegreeOfRotation =
                                Math.toDegrees(atan2(deltaX, deltaY).toDouble()).toFloat()

                            Log.i(
                                "MotionEvent",
                                "Total degree of rotation is $totalDegreeOfRotation  " +
                                        "first x : "
                            )
                            totalDegreeOfRotation
                        }
                    // Rotate the ViewGroup
                    rotation += rotatedDegree
                }
            }
        }
    }
    true
}

private val mainViewHolder = FrameLayout(context).apply {
    layoutParams =
        FrameLayout.LayoutParams(
            LayoutParams.WRAP_CONTENT,
            LayoutParams.WRAP_CONTENT,
        )

}

private val mainFrameBoundaryPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    color = Color.BLACK
    strokeWidth = 2.dp
    style = Paint.Style.STROKE
}

private val frameLayoutRectangle = RectF()

init {
    setOnTouchListener(motionEventHandler)
    setWillNotDraw(false)
}

override fun onAttachedToWindow() {
    super.onAttachedToWindow()
    addView(mainViewHolder)
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    measureChild(mainViewHolder, widthMeasureSpec, heightMeasureSpec)
    setMeasuredDimension(
        resolveSize(
            mainViewHolder.measuredWidth,
            widthMeasureSpec
        ),
        resolveSize(mainViewHolder.height, heightMeasureSpec)
    )
}

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    var x = 0
    mainViewHolder.layout(x, t, x + mainViewHolder.measuredWidth, mainViewHolder.measuredHeight)
    x += mainViewHolder.measuredWidth

    frameLayoutRectangle.set(
        0f, 0f,
        x.toFloat(),
        mainViewHolder.measuredHeight.toFloat()
    )
}

override fun dispatchDraw(canvas: Canvas?) {
    super.dispatchDraw(canvas)

    if (drawFrame)
        canvas!!.apply {
            drawRoundRect(frameLayoutRectangle, 2.dp, 2.dp, mainFrameBoundaryPaint)
        }
}

fun addToFrame(view: View) {
    // Let the canvas draw it's rectangle meaning that view is getting edited
    drawFrame = true
    // Add the view that's going to get edited to the FrameLayout
    mainViewHolder.addView(view)
}

fun showFrameAroundView() {
    // Show the rectangle frame around the view
    if (!drawFrame) {
        drawFrame = true
        invalidate()
    }
}

fun hideFrameAroundView() {
    
    // Hide the rectangle around the view (meaning it's not longer in editing state)
    if (drawFrame) {
        drawFrame = false
        invalidate()
    }
}

fun doesHaveChild(): Boolean {
    return childCount > 0
}

}

如果您能帮助我更好地实现该场景,我将不胜感激。

经过一天的反复试验和网上搜索,我终于找到了解决方案。 问题是我在计算中没有使用原始的 x 和 y。

这是修复它的 MotionEvent 处理程序代码:

 private val motionEventHandler: (view: View, event: MotionEvent) -> Boolean = { v, event ->
    // For scaling
    scaleDetector.onTouchEvent(event)
    val pointerCount = event.pointerCount
    when (event.actionMasked) {
        MotionEvent.ACTION_DOWN -> {
            if (drawFrame) {
                initialX = v.x - event.rawX
                initialY = v.y - event.rawY
            }
            performClick()
        }
        MotionEvent.ACTION_MOVE -> {
            // If view is in editing state (got clicked)
            if (drawFrame) {

                /* Moving the view by touch */

                // and if there is only 1 pointer on the screen
                if (pointerCount == 1) {

                    val viewParent = parent as ViewGroup

                    // Move the view
                    v.x = event.rawX + initialX
                    v.y = event.rawY + initialY

                    // Don't let the view go beyond the phone's display and limit it's x and y
                    viewParent.let { parent ->
                        val parentHeight = parent.height

                        if ((v.y + v.height) >= parentHeight) v.y =
                            (parentHeight - v.height).toFloat()

                        if (v.y <= parent.y) v.y = parent.y
                    }
                }

                /* Rotating the view by touch */
                // If there are total of two pointer on the screen
                if (pointerCount == 2) {
                    rotatedDegree =
                        event.run { /* <----- I think problem is in that code block */
                            // Get the first pointer x and y
                            val (firstX, firstY) = getPointerInfoAt(getPointerId(0))
                            // Get the second pointer x and y
                            val (secondX, secondY) = getPointerInfoAt(getPointerId(1))

                            // Calculate the difference between those points
                            val deltaX = firstX - secondX
                            val deltaY = secondY - firstY

                            // Get the total degree that view got rotated
                            val totalDegreeOfRotation =
                                Math.toDegrees(atan2(deltaX, deltaY).toDouble()).toFloat()

                            Log.i(
                                "MotionEvent",
                                "Total degree of rotation is $totalDegreeOfRotation  " +
                                        "first x : "
                            )
                            totalDegreeOfRotation
                        }
                    // Rotate the ViewGroup
                    rotation += rotatedDegree
                }
            }
        }
    }
    true
}