Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - Pinch zoom ontouch event coordinates

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.

like image 669
dkniffin Avatar asked Jan 18 '23 04:01

dkniffin


2 Answers

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

like image 157
dkniffin Avatar answered Jan 20 '23 15:01

dkniffin


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()

like image 42
EyalBellisha Avatar answered Jan 20 '23 17:01

EyalBellisha