Implementing pinch-zoom and pan/drag in an Android view on the canvas

by vivin

Now this change will make sure that the user cannot pan past the top, left, bottom, and right bounds of the canvas. There is one other, slight improvement that we can make. Recall that I mentioned earlier that the MotionEvent.ACTION_MOVE event gets triggered event when the finger is not moving. This leads to unnecessary redrawing of the canvas. If we kept track of the distance that the finger moves, we could be sure that we redraw the canvas only when needed:

public class ZoomView extends View {

    ...
    ...

    //This flag reflects whether the finger was actually dragged across the screen
    private boolean dragged = true;
    ...
    ...

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction() & MotionEvent.ACTION_MASK) {

            ...
            ...

            case MotionEvent.ACTION_MOVE:               
                translateX = event.getX() - startX;
                translateY = event.getY() - startY;
 
                //We cannot use startX and startY directly because we have adjusted their values using the previous translation values. This is why we need to add those
                //values to startX and startY so that we can get the actual coordinates of the finger.
                double distance = Math.sqrt(Math.pow(event.getX() - (startX + previousTranslateX), 2) + Math.pow(event.getY() - (startY + previousTranslateY), 2));
 
                if(distance > 0) {
                   dragged = true;
                }               

                break;

            case MotionEvent.ACTION_POINTER_DOWN:
                mode = ZOOM;
                break;

            case MotionEvent.ACTION_UP:
                mode = NONE;
                dragged = false;
                previousTranslateX = translateX;
                previousTranslateY = translateY;
                break;

            case MotionEvent.ACTION_POINTER_UP:
                mode = DRAG;
                previousTranslateX = translateX;
                previousTranslateY = translateY;
                break;       
        }

        detector.onTouchEvent(event);

        //We redraw the canvas only in the following cases:
        //
        // o The mode is ZOOM 
        //        OR
        // o The mode is DRAG and the scale factor is not equal to 1 (meaning we have zoomed) and dragged is
        //   set to true (meaning the finger has actually moved)
        if ((mode == DRAG && scaleFactor != 1f && dragged) || mode == ZOOM) {
            invalidate();
        }

        return true;
    }
}

With this change, the canvas is redrawn only when it needs to be redrawn. With this code in place you should be able to perform zooming and panning on your canvas in your view. There is still one drawback that I haven’t taken care of. Usually when you zoom, you want to center on the area that you’re zooming in on. The implementation here doesn’t do that. There is a way to do it, and it involves using the getFocusX() and getFocusY() methods on ScaleGestureDetector. These two values give you the focal point of the gesture, and you can use that to make sure that you are centered on that focal point. I haven’t figured out how to do it exactly, but if/when I do, I’ll make another post about that.