Skip to content

Instantly share code, notes, and snippets.

@mformetal
Created March 8, 2016 05:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mformetal/d32ad1ab147c519c2711 to your computer and use it in GitHub Desktop.
Save mformetal/d32ad1ab147c519c2711 to your computer and use it in GitHub Desktop.
My solution to the Pinch-To-Zoom problem
// Defines the actions of the Zoomer class. Feel free to implement as you see fit
public interface MotionEventHandler {
// Defines which "Mode" the user is in. NONE means they have not taken any action, DRAG means tehy're dragging,
// ZOOM means they're zooming
enum Mode {
NONE,
DRAG,
ZOOM
}
// Global definition of what pointerIndex will be seen as invalid
int INVALID_POINTER = -1;
// Self-explanatory touchevent callbacks
void onTouchDown(MotionEvent event);
void onPointerDown(MotionEvent event);
void onTouchMove(MotionEvent event);
void onPointerUp(MotionEvent event);
void onTouchUp(MotionEvent event);
void onCancel(MotionEvent event);
// Use this to set what "Mode" the user is in.
void setMode(Mode value);
Mode getMode();
// Define here if you don't want all the functionality of Zoomer. If, for example, you don't want the user to be
// able to DRAG the view, implement this by returning Arrays.asList(Mode.DRAG);
List<Mode> excludeModes();
// Helper methods to determine spacing of events. Left it to be implemented here
double distance(MotionEvent event);
void midPoint(PointF pointF, MotionEvent event);
float angle(MotionEvent event);
}
Zoomer zoomer = new Zoomer(imageView);
// It's that easy!
// Set this on an ImageView and you get all zooming, dragging, plus rotating functionality
public class Zoomer implements MotionEventHandler {
private final ImageView imageView;
private final Matrix savedMatrix, matrix;
private PointF startPoint, midPoint;
private float lastX, lastY;
private int activePointer;
private double oldDistance = 1f;
private float lastRotation;
private Mode mode = Mode.NONE;
public Zoomer(final ImageView view) {
imageView = view;
imageView.setScaleType(ImageView.ScaleType.MATRIX);
imageView.setOnTouchListener(listener);
savedMatrix = new Matrix();
matrix = new Matrix();
startPoint = new PointF();
midPoint = new PointF();
}
@Override
public void onTouchDown(MotionEvent event) {
float x = event.getX(), y = event.getY();
savedMatrix.set(matrix);
setMode(Mode.DRAG);
startPoint.set(x, y);
activePointer = event.getPointerId(0);
lastX = x;
lastY = y;
}
@Override
public void onPointerDown(MotionEvent event) {
if (event.getPointerCount() <= 2) {
oldDistance = distance(event);
if (oldDistance > 10f) {
savedMatrix.set(matrix);
midPoint(midPoint, event);
setMode(Mode.ZOOM);
lastRotation = angle(event);
}
}
}
@Override
public void onTouchMove(MotionEvent event) {
final int pointerIndex = event.findPointerIndex(activePointer);
float x = event.getX(pointerIndex), y = event.getY(pointerIndex);
if (getMode() == Mode.DRAG) {
matrix.set(savedMatrix);
matrix.postTranslate(x - startPoint.x, y - startPoint.y);
} else if (getMode() == Mode.ZOOM) {
if (event.getPointerCount() == 2) {
double newDist = distance(event);
if (newDist > 10f) {
matrix.set(savedMatrix);
double scale = (newDist / oldDistance);
matrix.postScale((float) scale, (float) scale, midPoint.x, midPoint.y);
}
float mCurrentRotation = angle(event);
matrix.postRotate(mCurrentRotation - lastRotation, midPoint.x, midPoint.y);
}
}
updateImageView();
lastX = x;
lastY = y;
}
@Override
public void onPointerUp(MotionEvent event) {
int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
int pointerId = event.getPointerId(pointerIndex);
if (pointerId == activePointer) {
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
lastX = event.getX(newPointerIndex);
lastY = event.getY(newPointerIndex);
activePointer = event.getPointerId(newPointerIndex);
}
setMode(Mode.NONE);
}
@Override
public void onTouchUp(MotionEvent event) {
activePointer = INVALID_POINTER;
lastX = event.getX();
lastY = event.getY();
}
@Override
public void onCancel(MotionEvent event) {
activePointer = INVALID_POINTER;
}
@Override
public void setMode(Mode value) {
if (!excludeModes().contains(value)) {
mode = value;
}
}
@Override
public Mode getMode() {
return mode;
}
@Override
public List<Mode> excludeModes() {
return Arrays.asList();
}
@Override
public double distance(MotionEvent event) {
double dx = event.getX(0) - event.getX(1);
double dy = event.getY(0) - event.getY(1);
return Math.sqrt(dx * dx + dy * dy);
}
@Override
public void midPoint(PointF pointF, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
pointF.set(x / 2, y / 2);
}
@Override
public float angle(MotionEvent event) {
double dx = (event.getX(0) - event.getX(1));
double dy = (event.getY(0) - event.getY(1));
double radians = Math.atan2(dy, dx);
return (float) Math.toDegrees(radians);
}
private void updateImageView() {
imageView.setImageMatrix(matrix);
imageView.invalidate();
}
private final View.OnTouchListener listener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
onTouchDown(event);
break;
case MotionEvent.ACTION_POINTER_DOWN:
onPointerDown(event);
break;
case MotionEvent.ACTION_MOVE:
onTouchMove(event);
break;
case MotionEvent.ACTION_UP:
onTouchUp(event);
break;
case MotionEvent.ACTION_POINTER_UP:
onPointerUp(event);
break;
case MotionEvent.ACTION_CANCEL:
onCancel(event);
break;
}
return true;
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment