How to Use Scale Gesture Detector for Pinch Zoom in Android

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

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!

How to detect the pinch-zoom event with OnGestureListener in Android?

Take a look at this post on the Android Developers blog: http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html

The later sections talk about using ScaleGestureDetector with code examples.

Android : Zoom by center pinch

I think that you will want to move to an implementation closer to the Android Dragging and Scaling examples (and to the similar question here).

This is the start of what you need. You can pan the view as before and now scale the view at the center of the pinch gesture. It is your base BoardView with logic to draw points on clicks from here. It should be pretty easy to draw your custom X and O icons instead of circles.

BoardView.java

public class BoardView extends View {

private static final int JUST_SCALED_DURATION = 100;
private static final int MAX_TOUCH_DURATION = 1000;
private static final int MAX_TOUCH_DISTANCE = 10;
private boolean stayedWithinTouchDistance;
private float firstTouchX, firstTouchY;
private long firstTouchTime, lastScaleTime;

private float posX, posY, lastTouchX, lastTouchY;
private ScaleGestureDetector scaleDetector;
private float minScaleFactor = 0.1f;
private float maxScaleFactor = 5.0f;
private float scaleFactor = 1.0f;
private float cellSize = 50.0f;
private int numCells = 50;

private ArrayList<Point> points;
private Paint linePaint, pointPaint;

public BoardView(Context context) {
this(context, null, 0);
}

public BoardView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public BoardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
points = new ArrayList<>();
linePaint = new Paint();
linePaint.setAntiAlias(true);
linePaint.setColor(-65536);
linePaint.setStrokeWidth(1.0f);
pointPaint = new Paint();
pointPaint.setAntiAlias(true);
pointPaint.setColor(-65536);
pointPaint.setStrokeWidth(1.0f);
posX = linePaint.getStrokeWidth();
posY = linePaint.getStrokeWidth();
scaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}

private static float distancePx(float x1, float y1, float x2, float y2) {
float dx = x1 - x2;
float dy = y1 - y2;
return (float) Math.sqrt(dx * dx + dy * dy);
}

private float distanceDp(float distancePx) {
return distancePx / getResources().getDisplayMetrics().density;
}

private Point coerceToGrid(Point point) {
point.x = (int) ((((int) (point.x / cellSize)) * cellSize) + (cellSize / 2));
point.y = (int) ((((int) (point.y / cellSize)) * cellSize) + (cellSize / 2));
return point;
}

@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
scaleDetector.onTouchEvent(event);
final int action = MotionEventCompat.getActionMasked(event);
if (action == MotionEvent.ACTION_DOWN) {
stayedWithinTouchDistance = true;
firstTouchX = lastTouchX = event.getRawX();
firstTouchY = lastTouchY = event.getRawY();
firstTouchTime = System.currentTimeMillis();
} else if (action == MotionEvent.ACTION_MOVE) {
float thisTouchX = event.getRawX();
float thisTouchY = event.getRawY();

boolean justScaled = System.currentTimeMillis() - lastScaleTime < JUST_SCALED_DURATION;
float distancePx = distancePx(firstTouchX, firstTouchY, thisTouchX, thisTouchY);
stayedWithinTouchDistance = stayedWithinTouchDistance &&
distanceDp(distancePx) < MAX_TOUCH_DISTANCE;

if (!stayedWithinTouchDistance && !scaleDetector.isInProgress() && !justScaled) {
posX += thisTouchX - lastTouchX;
posY += thisTouchY - lastTouchY;
invalidate();
}

lastTouchX = thisTouchX;
lastTouchY = thisTouchY;
} else if (action == MotionEvent.ACTION_UP) {
long touchDuration = System.currentTimeMillis() - firstTouchTime;
if (touchDuration < MAX_TOUCH_DURATION && stayedWithinTouchDistance) {
int[] location = {0, 0};
getLocationOnScreen(location);
float x = ((lastTouchX - posX - location[0]) / scaleFactor);
float y = ((lastTouchY - posY - location[1]) / scaleFactor);
points.add(coerceToGrid(new Point((int) x, (int) y)));
invalidate();
}
}
return true;
}

private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
lastScaleTime = System.currentTimeMillis();
float scale = detector.getScaleFactor();
scaleFactor = Math.max(minScaleFactor, Math.min(scaleFactor * scale, maxScaleFactor));
if (scaleFactor > minScaleFactor && scaleFactor < maxScaleFactor) {
float centerX = detector.getFocusX();
float centerY = detector.getFocusY();
float diffX = centerX - posX;
float diffY = centerY - posY;
diffX = diffX * scale - diffX;
diffY = diffY * scale - diffY;
posX -= diffX;
posY -= diffY;
invalidate();
return true;
}
return false;
}
}

private float getScaledCellSize() {
return scaleFactor * cellSize;
}

private float getScaledBoardSize() {
return numCells * getScaledCellSize();
}

private void drawBoard(Canvas canvas) {
for (int i = 0; i <= numCells; i++) {
float total = getScaledBoardSize();
float offset = getScaledCellSize() * i;
canvas.drawLine(offset, 0, offset, total, linePaint);
canvas.drawLine(0, offset, total, offset, linePaint);
}
}

private void drawPoints(Canvas canvas) {
for (Point point : points) {
float x = point.x * scaleFactor;
float y = point.y * scaleFactor;
float r = getScaledCellSize() / 4;
canvas.drawCircle(x, y, r, pointPaint);
}
}

@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
float total = getScaledBoardSize();
float edge = linePaint.getStrokeWidth();
posX = Math.max(Math.min(edge, getWidth() - total - edge), Math.min(edge, posX));
posY = Math.max(Math.min(edge, getHeight() - total - edge), Math.min(edge, posY));

canvas.save();
canvas.translate(posX, posY);
drawBoard(canvas);
drawPoints(canvas);
canvas.restore();
}

}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.example.client.BoardView
android:layout_width="match_parent"
android:layout_height="match_parent"/>

</FrameLayout>

How to fix pinch zoom focal point in a custom view?

Using Matrix is a really better idea - the code is much more simple and you dont have to prove your math skills ;-), see how Matrix#postTranslate and Matrix#postScale methods are used:

class MyView extends View {
private static final String TAG = "MyView";

private final ScaleGestureDetector mScaleDetector;
private final GestureDetector mGestureDetector;

private final Drawable mBoard;
private final float mBoardWidth;
private final float mBoardHeight;
private Matrix mMatrix;

public MyView(Context context) {
this(context, null, 0);
}

public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);

mBoard = ResourcesCompat.getDrawable(context.getResources(), R.drawable.chrome, null);
mBoardWidth = mBoard.getIntrinsicWidth();
mBoardHeight = mBoard.getIntrinsicHeight();
mBoard.setBounds(0, 0, (int) mBoardWidth, (int) mBoardHeight);

mMatrix = new Matrix();

mScaleDetector = new ScaleGestureDetector(context, scaleListener);
mGestureDetector = new GestureDetector(context, listener);
}

ScaleGestureDetector.OnScaleGestureListener scaleListener = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector scaleDetector) {
float factor = scaleDetector.getScaleFactor();
mMatrix.postScale(factor, factor, getWidth() / 2f, getHeight() / 2f);
ViewCompat.postInvalidateOnAnimation(MyView.this);
return true;
}
};

GestureDetector.OnGestureListener listener = new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dX, float dY) {
mMatrix.postTranslate(-dX, -dY);
ViewCompat.postInvalidateOnAnimation(MyView.this);
return true;
}
};

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
float scale = Math.max(w / mBoardWidth, h / mBoardHeight);
mMatrix.setScale(scale, scale);
mMatrix.postTranslate((w - scale * mBoardWidth) / 2f, (h - scale * mBoardHeight) / 2f);
}

@Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.concat(mMatrix);
mBoard.draw(canvas);
canvas.restore();
}

@Override
@SuppressLint("ClickableViewAccessibility")
public boolean onTouchEvent(MotionEvent e) {
mGestureDetector.onTouchEvent(e);
mScaleDetector.onTouchEvent(e);
return true;
}
}

android pinch zoom

Updated Answer

Code can be found here : official-doc

Answer Outdated

Check out the following links which may help you

Best examples are provided in the below links, which you can refactor to meet your requirements.

  1. Implementing the pinch zoom gesture

  2. Android-pinch

  3. GestureDetector.SimpleOnGestureListener



Related Topics



Leave a reply



Submit