We have the red dot in (100; 100) coordinates. If we click to red dot after dragging - it will save it's (100; 100) coordinates. But if we scale in or out it will have coordinates completely different from (100; 100).
How to calculate x and y correctly after scaling?
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 and y coordinates are wrong after scaling. They keep their position, but it's not that expected.