Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing pinch zoom and drag using Android's build in gesture listener and scale listener

Tags:

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.

like image 421
chochim Avatar asked Oct 17 '13 05:10

chochim


1 Answers

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!

like image 192
Tore Rudberg Avatar answered Oct 02 '22 23:10

Tore Rudberg