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