title: Android pinch zoom. Focus point / image shifting when pinching
body: <p>I've been working on creating a custom view with a really slick pinch zoom. So far I have got the zooming and panning working, but when I pinch zoom on a different area of the screen the image moves to the opposite position the screen. </p>
  
  <p>For example if the image is on the left and I pinch zoom on the left, the image will shift to the right. </p>
  
  <p>I'm sure I am making a simple error somewhere, but I can't for the life of me figure out where the problem is. I've tried masses of changes over the last few days and spent hours searching through SO to no avail.</p>
  
  <p>Here's the code I have so far:</p>
  
  <pre><code>private class PinchView extends View {
  
      private ScaleGestureDetector mScaleDetector;
      private float mScaleFactor = 1.f;
      private static final int INVALID_POINTER_ID = -1;
  
      // The ‘active pointer’ is the one currently moving our object.
      private int mActivePointerId = INVALID_POINTER_ID;
  
      private Drawable mIcon;
      private float mPosX;
      private float mPosY;
  
      private float mLastTouchX;
      private float mLastTouchY;
  
  
      private float focusX;
      private float focusY;
  
  
  
      public PinchView(Context context) {
          super(context);
          mIcon = context.getResources().getDrawable(R.drawable.ic_launcher);
          mIcon.setBounds(0, 0, mIcon.getIntrinsicWidth(), mIcon.getIntrinsicHeight());
  
          // Create our ScaleGestureDetector
          mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
      }
  
      @Override
      public boolean onTouchEvent(MotionEvent ev) {
          // Let the ScaleGestureDetector inspect all events.
          mScaleDetector.onTouchEvent(ev);
  
          final int action = ev.getAction();
          switch (action &amp; MotionEvent.ACTION_MASK) {
          case MotionEvent.ACTION_DOWN: {
              final float x = ev.getX()/mScaleFactor;
              final float y = ev.getY()/mScaleFactor;
  
              mLastTouchX = x;
              mLastTouchY = y;
              mActivePointerId = ev.getPointerId(0);
              break;
          }
  
          case MotionEvent.ACTION_MOVE: {
              final int pointerIndex = ev.findPointerIndex(mActivePointerId);
              final float x = ev.getX(pointerIndex)/mScaleFactor;
              final float y = ev.getY(pointerIndex)/mScaleFactor;
  
              // Only move if the ScaleGestureDetector isn't processing a gesture.
              if (!mScaleDetector.isInProgress()) {
                  final float dx = x - mLastTouchX;
                  final float dy = y - mLastTouchY;
  
                  mPosX += dx;
                  mPosY += dy;
  
                  invalidate();
              }
  
              mLastTouchX = x;
              mLastTouchY = y;
  
              break;
          }
  
          case MotionEvent.ACTION_UP: {
              mActivePointerId = INVALID_POINTER_ID;
              break;
          }
  
          case MotionEvent.ACTION_CANCEL: {
              mActivePointerId = INVALID_POINTER_ID;
              break;
          }
  
          case MotionEvent.ACTION_POINTER_UP: {
              final int pointerIndex = (ev.getAction() &amp; MotionEvent.ACTION_POINTER_INDEX_MASK) 
                      &gt;&gt; MotionEvent.ACTION_POINTER_INDEX_SHIFT;
              final int pointerId = ev.getPointerId(pointerIndex);
              if (pointerId == mActivePointerId) {
                  // This was our active pointer going up. Choose a new
                  // active pointer and adjust accordingly.
                  final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                  mLastTouchX = ev.getX(newPointerIndex)/mScaleFactor;
                  mLastTouchY = ev.getY(newPointerIndex)/mScaleFactor;
                  mActivePointerId = ev.getPointerId(newPointerIndex);
              }
  
  
  
              break;
          }
          }
  
          return true;
      }
  
      @Override
      public void onDraw(Canvas canvas) {
          super.onDraw(canvas);
  
          canvas.save();
  
       //   canvas.scale(mScaleFactor, mScaleFactor);
          canvas.scale(mScaleFactor, mScaleFactor, focusX, focusY);
          canvas.translate(mPosX, mPosY);
          mIcon.draw(canvas);
          canvas.restore();
      }
  
      private float lastFocusX = -1;
      private float lastFocusY = -1;
  
      private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
  
  
  
          @Override
          public boolean onScaleBegin(ScaleGestureDetector detector) {
  
          //  float x = detector.getFocusX();
          //  float y = detector.getFocusY();
  
              lastFocusX = -1;
              lastFocusY = -1;
  
  
              return true;
          }
  
          @Override
          public boolean onScale(ScaleGestureDetector detector) {
              mScaleFactor *= detector.getScaleFactor();
  
              focusX = detector.getFocusX();
              focusY = detector.getFocusY();
  
              if(lastFocusX == -1) lastFocusX = focusX;
              if(lastFocusY == -1) lastFocusY = focusY;
  
  
              mPosX += (focusX - lastFocusX);
              mPosY += (focusY - lastFocusY);
  
  
  
  
              // Don't let the object get too small or too large.
              mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
  
            lastFocusX = focusX;
            lastFocusY = focusY;
  
              invalidate();
              return true;
          }
  
  
  
      }
  }
  </code></pre>