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.
I appreciate how clear you tried to explain everything. Thank you!
I want to do a “zoomable paint”, I mean a paint that I can zoom/zoom out and pan/drag the canvas and then draw on it.
I have a problem that I can’t solve: when I draw while the canvas is zoomed, I retrieve the X and Y coordinate and effectively drawing it on the canvas. But these coordinates are not correct because of the zoomed canvas.
I tried to correct these (multiply by (zoomHeigh/screenHeight)) but I can’t find a way to retrieve where I must draw on the original/none-zoomed screen
Whenever I try to implement above method the screen jitters on account of zoom effect but does not zoom essentially. I am drawing circle points on canvas and on zooming I want them to scale further apart. I don’t know if I tend to custom draw those circle points based on dimensions of zoomed canvas why is the overall effect a mere jitter ?