Wrong coordinates after scaling canvas
up vote
-1
down vote
favorite
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.
|
show 13 more comments
up vote
-1
down vote
favorite
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.
why do you want tocanvas.scaleandcanvas.translate? cannot you simplycanvas.drawCircle(canterX, canterY, scaledRadius, paint)?
– pskink
Nov 10 at 7:16
Because the red dot is just an example. I'm going to draw multiple objects, not only one red dot. And moving objects instead of viewport will break everything! I need to save object coordinates to correctly handle click events on them after drag/scale.
– badadin
Nov 10 at 8:00
then useMatrixAPI andCanvas#concatmethod
– pskink
Nov 10 at 8:06
I thought about it, but I did not find an example with drag/zoom/click. If you show me an example, I will very appreciate.
– badadin
Nov 10 at 8:11
And what's wrong with canvas.scale and canvas.translate? Why do you recommend not tot use them?
– badadin
Nov 10 at 8:28
|
show 13 more comments
up vote
-1
down vote
favorite
up vote
-1
down vote
favorite
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.
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.
edited Nov 11 at 9:32
asked Nov 10 at 6:36
badadin
6116
6116
why do you want tocanvas.scaleandcanvas.translate? cannot you simplycanvas.drawCircle(canterX, canterY, scaledRadius, paint)?
– pskink
Nov 10 at 7:16
Because the red dot is just an example. I'm going to draw multiple objects, not only one red dot. And moving objects instead of viewport will break everything! I need to save object coordinates to correctly handle click events on them after drag/scale.
– badadin
Nov 10 at 8:00
then useMatrixAPI andCanvas#concatmethod
– pskink
Nov 10 at 8:06
I thought about it, but I did not find an example with drag/zoom/click. If you show me an example, I will very appreciate.
– badadin
Nov 10 at 8:11
And what's wrong with canvas.scale and canvas.translate? Why do you recommend not tot use them?
– badadin
Nov 10 at 8:28
|
show 13 more comments
why do you want tocanvas.scaleandcanvas.translate? cannot you simplycanvas.drawCircle(canterX, canterY, scaledRadius, paint)?
– pskink
Nov 10 at 7:16
Because the red dot is just an example. I'm going to draw multiple objects, not only one red dot. And moving objects instead of viewport will break everything! I need to save object coordinates to correctly handle click events on them after drag/scale.
– badadin
Nov 10 at 8:00
then useMatrixAPI andCanvas#concatmethod
– pskink
Nov 10 at 8:06
I thought about it, but I did not find an example with drag/zoom/click. If you show me an example, I will very appreciate.
– badadin
Nov 10 at 8:11
And what's wrong with canvas.scale and canvas.translate? Why do you recommend not tot use them?
– badadin
Nov 10 at 8:28
why do you want to
canvas.scale and canvas.translate? cannot you simply canvas.drawCircle(canterX, canterY, scaledRadius, paint)?– pskink
Nov 10 at 7:16
why do you want to
canvas.scale and canvas.translate? cannot you simply canvas.drawCircle(canterX, canterY, scaledRadius, paint)?– pskink
Nov 10 at 7:16
Because the red dot is just an example. I'm going to draw multiple objects, not only one red dot. And moving objects instead of viewport will break everything! I need to save object coordinates to correctly handle click events on them after drag/scale.
– badadin
Nov 10 at 8:00
Because the red dot is just an example. I'm going to draw multiple objects, not only one red dot. And moving objects instead of viewport will break everything! I need to save object coordinates to correctly handle click events on them after drag/scale.
– badadin
Nov 10 at 8:00
then use
Matrix API and Canvas#concat method– pskink
Nov 10 at 8:06
then use
Matrix API and Canvas#concat method– pskink
Nov 10 at 8:06
I thought about it, but I did not find an example with drag/zoom/click. If you show me an example, I will very appreciate.
– badadin
Nov 10 at 8:11
I thought about it, but I did not find an example with drag/zoom/click. If you show me an example, I will very appreciate.
– badadin
Nov 10 at 8:11
And what's wrong with canvas.scale and canvas.translate? Why do you recommend not tot use them?
– badadin
Nov 10 at 8:28
And what's wrong with canvas.scale and canvas.translate? Why do you recommend not tot use them?
– badadin
Nov 10 at 8:28
|
show 13 more comments
1 Answer
1
active
oldest
votes
up vote
0
down vote
accepted
At first I decided to use this solution and it worked as expected. But then I used solution that pskink advised me to use. It simpler, shorter and rather more correct then my previous choice. Example:
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--
}
}
}
}
add a comment |
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
0
down vote
accepted
At first I decided to use this solution and it worked as expected. But then I used solution that pskink advised me to use. It simpler, shorter and rather more correct then my previous choice. Example:
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--
}
}
}
}
add a comment |
up vote
0
down vote
accepted
At first I decided to use this solution and it worked as expected. But then I used solution that pskink advised me to use. It simpler, shorter and rather more correct then my previous choice. Example:
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--
}
}
}
}
add a comment |
up vote
0
down vote
accepted
up vote
0
down vote
accepted
At first I decided to use this solution and it worked as expected. But then I used solution that pskink advised me to use. It simpler, shorter and rather more correct then my previous choice. Example:
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--
}
}
}
}
At first I decided to use this solution and it worked as expected. But then I used solution that pskink advised me to use. It simpler, shorter and rather more correct then my previous choice. Example:
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--
}
}
}
}
edited Nov 12 at 18:15
answered Nov 11 at 9:30
badadin
6116
6116
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53236594%2fwrong-coordinates-after-scaling-canvas%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
why do you want to
canvas.scaleandcanvas.translate? cannot you simplycanvas.drawCircle(canterX, canterY, scaledRadius, paint)?– pskink
Nov 10 at 7:16
Because the red dot is just an example. I'm going to draw multiple objects, not only one red dot. And moving objects instead of viewport will break everything! I need to save object coordinates to correctly handle click events on them after drag/scale.
– badadin
Nov 10 at 8:00
then use
MatrixAPI andCanvas#concatmethod– pskink
Nov 10 at 8:06
I thought about it, but I did not find an example with drag/zoom/click. If you show me an example, I will very appreciate.
– badadin
Nov 10 at 8:11
And what's wrong with canvas.scale and canvas.translate? Why do you recommend not tot use them?
– badadin
Nov 10 at 8:28