Created
March 8, 2016 05:19
-
-
Save mformetal/d32ad1ab147c519c2711 to your computer and use it in GitHub Desktop.
My solution to the Pinch-To-Zoom problem
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Zoomer zoomer = new Zoomer(imageView); | |
// It's that easy! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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