I am trying to implement pinch zoom and drag using Android's gesture listener and scale listener. The problem is that when I perform pinch zoom, the image (which I am trying to zoom) bounces to a particular location. Also the zoom position is not centered. The following code demonstrates what I am trying to achieve. Any idea why the image is jumping (and how to correct it) ?
public class CustomView extends View { Bitmap image; int screenHeight; int screenWidth; Paint paint; GestureDetector gestures; ScaleGestureDetector scaleGesture; float scale = 1.0f; float horizontalOffset, verticalOffset; int NORMAL = 0; int ZOOM = 1; int DRAG = 2; boolean isScaling = false; float touchX, touchY; int mode = NORMAL; public CustomView(Context context) { super(context); //initializing variables image = BitmapFactory.decodeResource(getResources(), R.drawable.image_name); //This is a full screen view screenWidth = getResources().getDisplayMetrics().widthPixels; screenHeight = getResources().getDisplayMetrics().heightPixels; paint = new Paint(); paint.setAntiAlias(true); paint.setFilterBitmap(true); paint.setDither(true); paint.setColor(Color.WHITE); scaleGesture = new ScaleGestureDetector(getContext(), new ScaleListener()); gestures = new GestureDetector(getContext(), new GestureListener()); mode = NORMAL; initialize(); } //Best fit image display on canvas private void initialize() { float imgPartRatio = image.getWidth() / (float) image.getHeight(); float screenRatio = (float) screenWidth / (float) screenHeight; if (screenRatio > imgPartRatio) { scale = ((float) screenHeight) / (float) (image.getHeight()); // fit height horizontalOffset = ((float) screenWidth - scale * (float) (image.getWidth())) / 2.0f; verticalOffset = 0; } else { scale = ((float) screenWidth) / (float) (image.getWidth()); // fit width horizontalOffset = 0; verticalOffset = ((float) screenHeight - scale * (float) (image.getHeight())) / 2.0f; } invalidate(); } @Override protected void onDraw(Canvas canvas) { canvas.save(); canvas.drawColor(0, Mode.CLEAR); canvas.drawColor(Color.WHITE); if(mode == DRAG || mode == NORMAL) { //This works perfectly as expected canvas.translate(horizontalOffset, verticalOffset); canvas.scale(scale, scale); canvas.drawBitmap(image, getMatrix(), paint); } else if (mode == ZOOM) { //PROBLEM AREA - when applying pinch zoom, //the image jumps to a position abruptly canvas.scale(scale, scale, touchX, touchY); canvas.drawBitmap(image, getMatrix(), paint); } canvas.restore(); } public class ScaleListener implements OnScaleGestureListener { @Override public boolean onScale(ScaleGestureDetector detector) { float scaleFactorNew = detector.getScaleFactor(); if (detector.isInProgress()) { touchX = detector.getFocusX(); touchY = detector.getFocusY(); scale *= scaleFactorNew; invalidate(0, 0, screenWidth, screenHeight); } return true; } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { isScaling = true; mode=ZOOM; return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { mode = NORMAL; isScaling = false; } } public class GestureListener implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener { @Override public boolean onDown(MotionEvent e) { isScaling = false; return true; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (!isScaling) { mode = DRAG; isScaling = false; horizontalOffset -= distanceX; verticalOffset -= distanceY; invalidate(0, 0, screenWidth, screenHeight); } else { mode = ZOOM; isScaling = true; } return true; } } @Override public boolean onTouchEvent(MotionEvent event) { scaleGesture.onTouchEvent(event); gestures.onTouchEvent(event); return true; } }
Thanks in advance.
I have implemented this behaviour, and I used a matrix to handle all the zooming and scrolling (and rotation, in my case). It makes for neat code and works like clockwork.
Store a Matrix as a class member:
Matrix drawMatrix;
Edit: Store old focus point, used to get the focus shift during scaling.
float lastFocusX; float lastFocusY;
Edit: Set lastFocus variables in onScaleBegin
@Override public boolean onScaleBegin(ScaleGestureDetector detector) { lastFocusX = detector.getFocusX(); lastFocusY = detector.getFocusY(); return true; }
Replace your onScale:
@Override public boolean onScale(ScaleGestureDetector detector) { Matrix transformationMatrix = new Matrix(); float focusX = detector.getFocusX(); float focusY = detector.getFocusY(); //Zoom focus is where the fingers are centered, transformationMatrix.postTranslate(-focusX, -focusY); transformationMatrix.postScale(detector.getScaleFactor(), detector.getScaleFactor()); /* Adding focus shift to allow for scrolling with two pointers down. Remove it to skip this functionality. This could be done in fewer lines, but for clarity I do it this way here */ //Edited after comment by chochim float focusShiftX = focusX - lastFocusX; float focusShiftY = focusY - lastFocusY; transformationMatrix.postTranslate(focusX + focusShiftX, focusY + focusShiftY); drawMatrix.postConcat(transformationMatrix); lastFocusX = focusX; lastFocusY = focusY; invalidate(); return true; }
Similarly in onScroll:
@Override public boolean onScroll(MotionEvent downEvent, MotionEvent currentEvent, float distanceX, float distanceY) { drawMatrix.postTranslate(-distanceX, -distanceY); invalidate(); return true; }
in onDraw; Draw with your drawMatrix:
canvas.drawBitmap(image, drawMatrix, paint);
Happy coding!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With