I'm trying to get the canvas coordinates for an android app that I'm creating. It works great until I add the code to use a scale focus point (following two lines):
scalePoint.setX((int) detector.getFocusX());
scalePoint.setY((int) detector.getFocusY());
Here's my source code for my view class:
package us.kniffin.Jigsaw;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
public class TestView extends View {
private float mLastTouchX;
private float mLastTouchY;
private float mPosX;
private float mPosY;
private Rect rect;
private float cX, cY; // circle coords
// Scaling objects
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
// The focus point for the scaling
private float scalePointX;
private float scalePointY;
public TestView(Context context) {
super(context);
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint p = new Paint();
p.setColor(Color.RED);
rect = canvas.getClipBounds();
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor, scalePointX, scalePointY);
canvas.translate(mPosX, mPosY);
canvas.drawCircle(cX, cY, 10, p);
canvas.restore();
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
mScaleDetector.onTouchEvent(ev);
final int action = ev.getAction();
switch(action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX()/mScaleFactor;// screen X position
final float y = ev.getY()/mScaleFactor;// screen Y position
cX = x - (rect.left * mScaleFactor) - mPosX; // canvas X
cY = y - (rect.top * mScaleFactor) - mPosY; // canvas Y
// Remember where we started
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_MOVE: {
final float x = ev.getX()/mScaleFactor;
final float y = ev.getY()/mScaleFactor;
cX = x - (rect.left * mScaleFactor) - mPosX; // canvas X
cY = y - (rect.top * mScaleFactor) - mPosY; // canvas Y
// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX; // change in X
final float dy = y - mLastTouchY; // change in Y
mPosX += dx;
mPosY += dy;
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_UP: {
mLastTouchX = 0;
mLastTouchY = 0;
invalidate();
}
}
return true;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
scalePointX = detector.getFocusX();
scalePointY = detector.getFocusY();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
invalidate();
return true;
}
}
}
Any ideas what I need to do to get this working?
Update: I replaced the code sample with another that has the same issue, but is simplified to the essentials
Update Again: The issue occurs after scaling. Before scaling, the coordinates are correct, but afterwards, the coordinates are too far to the right and below where you click. It appears that the more you zoom out, the more wrong they get.
Haha! Success! It took me almost all day, but I figured it out with a bunch of guess and checks.
Here's the bit of code that I needed:
case MotionEvent.ACTION_DOWN: {
final float x = (ev.getX() - scalePointX)/mScaleFactor;
final float y = (ev.getY() - scalePointY)/mScaleFactor;
cX = x - mPosX + scalePointX; // canvas X
cY = y - mPosY + scalePointY; // canvas Y
[snip]
}
And a similar bit of code for ACTION_MOVE
Whenever you use translate, scale, rotate (etc.) you are changing the canvas' matrix. Generally, this matrix is used to tell you for each (x,y) where it is on the canvas. When you create a canvas the matrix is the identity matrix: (1 ) ( 1 ) ( 1) But whenever you make one of these changes this matrix also changes. If you want to know the right coordinates then before scaling/rotating you should use canvas.save() and after scaling use canvas.restore()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With