缩放画布后坐标错误

时间:2018-11-10 06:36:22

标签: android kotlin android-canvas

我们在(100; 100)坐标中有一个红点。如果我们在拖动后单击到红点,它将保存(100; 100)坐标。但是,如果我们放大或缩小,其坐标将完全不同于(100; 100)。

缩放后如何正确计算x和y?

class CanvasView(context: Context, attrs: AttributeSet?) : View(context, attrs) {

    companion object {
        private const val INVALID_POINTER_ID = -1
    }

    private var posX: Float = 0f
    private var posY: Float = 0f

    private var lastTouchX: Float = 0f
    private var lastTouchY: Float = 0f
    private var activePointerId = INVALID_POINTER_ID

    private val scaleDetector: ScaleGestureDetector
    private var scaleFactor = 1f

    private var prevMotionType = MotionEvent.ACTION_DOWN
    private var prevX = 0f
    private var prevY = 0f

    private val paint: Paint = Paint()

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

    init {
        scaleDetector = ScaleGestureDetector(context, ScaleListener())

        paint.strokeWidth = 1f
        paint.color = Color.RED
    }

    public override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.save()
        canvas.scale(scaleFactor, scaleFactor, pivotX, pivotY)
        canvas.translate(posX, posY)
        canvas.drawCircle(100f, 100f, 10f, paint)
        canvas.restore()
    }

    override fun onTouchEvent(ev: MotionEvent): Boolean {
        scaleDetector.onTouchEvent(ev)

        val action = ev.action
        when (action and MotionEvent.ACTION_MASK) {
            MotionEvent.ACTION_DOWN -> {
                val x = ev.x
                val y = ev.y

                lastTouchX = x
                lastTouchY = y
                activePointerId = ev.getPointerId(0)

                calculateIfClicked(ev)
            }

            MotionEvent.ACTION_MOVE -> {
                val pointerIndex = ev.findPointerIndex(activePointerId)
                val x = ev.getX(pointerIndex)
                val y = ev.getY(pointerIndex)

                if (!scaleDetector.isInProgress) {
                    val dx = x - lastTouchX
                    val dy = y - lastTouchY

                    posX += dx / scaleFactor
                    posY += dy / scaleFactor

                    invalidate()
                }

                lastTouchX = x
                lastTouchY = y

                calculateIfClicked(ev)
            }

            MotionEvent.ACTION_UP -> {
                activePointerId = INVALID_POINTER_ID

                calculateIfClicked(ev)
            }

            MotionEvent.ACTION_CANCEL -> {
                activePointerId = INVALID_POINTER_ID
            }

            MotionEvent.ACTION_POINTER_UP -> {
                val pointerIndex =
                    ev.action and MotionEvent.ACTION_POINTER_INDEX_MASK shr MotionEvent.ACTION_POINTER_INDEX_SHIFT
                val pointerId = ev.getPointerId(pointerIndex)
                if (pointerId == activePointerId) {
                    val newPointerIndex = if (pointerIndex == 0) 1 else 0
                    lastTouchX = ev.getX(newPointerIndex)
                    lastTouchY = ev.getY(newPointerIndex)
                    activePointerId = ev.getPointerId(newPointerIndex)
                }
            }
        }

        return true
    }

    private fun calculateIfClicked(ev: MotionEvent) {
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                prevMotionType = MotionEvent.ACTION_DOWN
                prevX = ev.x
                prevY = ev.y
            }

            MotionEvent.ACTION_MOVE -> prevMotionType = MotionEvent.ACTION_MOVE

            MotionEvent.ACTION_UP -> {
                val delta = Math.max(
                    Math.abs(Math.abs(ev.x) - Math.abs(prevX)),
                    Math.abs(Math.abs(ev.y) - Math.abs(prevY))
                )

                if (prevMotionType == MotionEvent.ACTION_DOWN ||
                    (prevMotionType == MotionEvent.ACTION_MOVE && delta < 5)
                ) {
                    val x = ev.x - posX * scaleFactor
                    val y = ev.y - posY * scaleFactor

                    Log.d("abcd", "x: $x, y: $y")
                }
            }
        }
    }

    private inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            scaleFactor *= detector.scaleFactor
            scaleFactor = Math.max(0.3f, Math.min(scaleFactor, 10.0f))

            invalidate()
            return true
        }
    }
}
缩放后,

x和y坐标错误。他们保持了自己的位置,但这不是人们所期望的。

1 个答案:

答案 0 :(得分:0)

起初,我决定使用此solution并按预期工作。但是后来我使用了pskink建议我使用的解决方案。它比我以前的选择更简单,更短,更正确。示例:

interface OnMatrixChangeListener {
    fun onChange(theMatrix: Matrix)
}

class ChartView2(context: Context, attrs: AttributeSet?) : View(context, attrs), OnMatrixChangeListener {

    private var theMatrix = Matrix()
    private var detector = MatrixGestureDetector(theMatrix, this)
    private var paint = Paint()
    private var colors = intArrayOf(Color.RED, Color.GREEN, Color.BLUE)
    private var colorNames = arrayOf("RED", "GREEN", "BLUE")
    private var centers = arrayOf(PointF(100f, 100f), PointF(400f, 100f), PointF(250f, 360f))

    var inverse = Matrix()
    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (event.action == MotionEvent.ACTION_DOWN) {
            theMatrix.invert(inverse)
            val pts = floatArrayOf(event.x, event.y)
            inverse.mapPoints(pts)
            for (i in colors.indices) {
                if (Math.hypot((pts[0] - centers[i].x).toDouble(), (pts[1] - centers[i].y).toDouble()) < 100)
                    Log.d("abcd", colorNames[i] + " circle clicked")
            }
        }
        detector.onTouchEvent(event)
        return true
    }

    override fun onDraw(canvas: Canvas) {
        canvas.concat(theMatrix)
        for (i in colors.indices) {
            paint.color = colors[i]
            canvas.drawCircle(centers[i].x, centers[i].y, 100f, paint)
        }
    }

    override fun onChange(theMatrix: Matrix) {
        invalidate()
    }
}

internal class MatrixGestureDetector(private val mMatrix: Matrix, listener: OnMatrixChangeListener) {

    private var ptpIdx = 0
    private val mTempMatrix = Matrix()
    private val mListener: OnMatrixChangeListener?
    private val mSrc = FloatArray(4)
    private val mDst = FloatArray(4)
    private var mCount: Int = 0

    init {
        this.mListener = listener
    }

    fun onTouchEvent(event: MotionEvent) {
        if (event.pointerCount > 2) {
            return
        }

        val action = event.actionMasked
        val index = event.actionIndex

        when (action) {
            MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
                val idx = index * 2
                mSrc[idx] = event.getX(index)
                mSrc[idx + 1] = event.getY(index)
                mCount++
                ptpIdx = 0
            }

            MotionEvent.ACTION_MOVE -> {
                for (i in 0 until mCount) {
                    val idx = ptpIdx + i * 2
                    mDst[idx] = event.getX(i)
                    mDst[idx + 1] = event.getY(i)
                }
                mTempMatrix.setPolyToPoly(mSrc, ptpIdx, mDst, ptpIdx, mCount)
                mMatrix.postConcat(mTempMatrix)
                mListener?.onChange(mMatrix)
                System.arraycopy(mDst, 0, mSrc, 0, mDst.size)
            }

            MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
                if (event.getPointerId(index) == 0) ptpIdx = 2
                mCount--
            }
        }
    }
}