Last active
July 25, 2017 08:32
-
-
Save wotomas/2ee2ae3bcd41a6e81b859ac4eda52cff to your computer and use it in GitHub Desktop.
ZoomableViewGroup with max min zoom scope and zoom boundaries
This file contains hidden or 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
package info.kimjihyok.customviewtestproject; | |
import android.content.Context; | |
import android.graphics.Canvas; | |
import android.graphics.Matrix; | |
import android.graphics.PointF; | |
import android.util.AttributeSet; | |
import android.view.MotionEvent; | |
import android.view.View; | |
import android.view.ViewGroup; | |
public class ZoomableViewGroup extends ViewGroup { | |
// Constants for MAX, MIN zoom | |
private static final int MAX_ZOOM = 2; | |
private static final int MIN_ZOOM = 1; | |
private static float MAX_TRANS_X; | |
private static float MAX_TRANS_Y; | |
private static float MIN_TRANS_X; | |
private static float MIN_TRANS_Y; | |
// these matrices will be used to zoom image | |
private Matrix matrix = new Matrix(); | |
private Matrix matrixInverse = new Matrix(); | |
private Matrix savedMatrix = new Matrix(); | |
// we can be in one of these 2 states | |
private static final int NONE = 0; | |
private static final int ZOOM = 1; | |
private int mode = NONE; | |
// remember some things for zooming | |
private PointF start = new PointF(); | |
private PointF mid = new PointF(); | |
private float oldDist = 1f; | |
private float[] lastEvent = null; | |
private float[] mDispatchTouchEventWorkingArray = new float[2]; | |
private float[] mOnTouchEventWorkingArray = new float[2]; | |
@Override | |
public boolean dispatchTouchEvent(MotionEvent ev) { | |
mDispatchTouchEventWorkingArray[0] = ev.getX(); | |
mDispatchTouchEventWorkingArray[1] = ev.getY(); | |
mDispatchTouchEventWorkingArray = screenPointsToScaledPoints(mDispatchTouchEventWorkingArray); | |
ev.setLocation(mDispatchTouchEventWorkingArray[0], | |
mDispatchTouchEventWorkingArray[1]); | |
return super.dispatchTouchEvent(ev); | |
} | |
private float[] scaledPointsToScreenPoints(float[] a) { | |
matrix.mapPoints(a); | |
return a; | |
} | |
private float[] screenPointsToScaledPoints(float[] a){ | |
matrixInverse.mapPoints(a); | |
return a; | |
} | |
public ZoomableViewGroup(Context context) { | |
super(context); | |
init(context); | |
} | |
public ZoomableViewGroup(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
init(context); | |
} | |
public ZoomableViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { | |
super(context, attrs, defStyleAttr); | |
init(context); | |
} | |
/** | |
* Determine the space between the first two fingers | |
*/ | |
private float spacing(MotionEvent event) { | |
try { | |
float x = event.getX(0) - event.getX(1); | |
float y = event.getY(0) - event.getY(1); | |
return (float)Math.sqrt(x * x + y * y); | |
} catch (Exception e) { | |
// It is a drag, do nothing | |
return 0; | |
} | |
} | |
/** | |
* Calculate the mid point of the first two fingers | |
*/ | |
private void midPoint(PointF point, MotionEvent event) { | |
float x = event.getX(0) + event.getX(1); | |
float y = event.getY(0) + event.getY(1); | |
point.set(x / 2, y / 2); | |
} | |
private void init(Context context){ | |
} | |
@Override | |
protected void onLayout(boolean changed, int l, int t, int r, int b) { | |
int childCount = getChildCount(); | |
for (int i = 0; i < childCount; i++) { | |
View child = getChildAt(i); | |
if (child.getVisibility() != GONE) { | |
child.layout(l, t, l+child.getMeasuredWidth(), t + child.getMeasuredHeight()); | |
} | |
} | |
} | |
@Override | |
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
super.onMeasure(widthMeasureSpec, heightMeasureSpec); | |
float[] values = new float[9]; | |
matrix.getValues(values); | |
int childCount = getChildCount(); | |
for (int i = 0; i < childCount; i++) { | |
View child = getChildAt(i); | |
if (child.getVisibility() != GONE) { | |
measureChild(child, widthMeasureSpec, heightMeasureSpec); | |
} | |
} | |
} | |
@Override | |
protected void dispatchDraw(Canvas canvas) { | |
// factor 2: max matrix: [scale, 0, scale * width - width], [0, scale, scale * height - height], [0, 0, 1.0] | |
// factor 2: min matrix: [scale, 0, (scale * width - width) * -1], [0, scale, (scale * height - height) * -1], [0, 0, 1.0] | |
float[] values = new float[9]; | |
matrix.getValues(values); | |
// set max trans x, y values according to scale value | |
// max matrix: [scale, 0, scale * width - width], [0, scale, scale * height - height], [0, 0, 1.0] | |
// min matrix: [scale, 0, (scale * width - width) * -1], [0, scale, (scale * height - height) * -1], [0, 0, 1.0] | |
MAX_TRANS_X = (values[Matrix.MSCALE_X] * getWidth()) - getWidth(); | |
MAX_TRANS_Y = (values[Matrix.MSCALE_X] * getHeight()) - getHeight(); | |
MIN_TRANS_X = ((values[Matrix.MSCALE_X] * getWidth()) - getWidth()) * -1; | |
MIN_TRANS_Y = ((values[Matrix.MSCALE_X] * getHeight()) - getHeight()) * -1; | |
// Log.d("dispatchDraw", "MIN MAX: " + "[" + MIN_TRANS_X + ", " + MAX_TRANS_X + "] [" + MIN_TRANS_Y + ", " + MAX_TRANS_Y + "]"); | |
// Log.d("dispatchDraw", matrix.toShortString()); | |
// limit transformation matrix value | |
if (values[Matrix.MTRANS_X] > MAX_TRANS_X) { | |
values[Matrix.MTRANS_X] = MAX_TRANS_X; | |
} | |
if (values[Matrix.MTRANS_Y] > MAX_TRANS_Y) { | |
values[Matrix.MTRANS_X] = MAX_TRANS_X; | |
} | |
if (values[Matrix.MTRANS_X] < MIN_TRANS_X) { | |
values[Matrix.MTRANS_X] = MIN_TRANS_X; | |
} | |
if (values[Matrix.MTRANS_X] < MIN_TRANS_Y) { | |
values[Matrix.MTRANS_X] = MIN_TRANS_Y; | |
} | |
matrix.setValues(values); | |
// Log.d("dispatchDraw", matrix.toShortString()); | |
canvas.save(); | |
canvas.setMatrix(matrix); | |
super.dispatchDraw(canvas); | |
canvas.restore(); | |
} | |
@Override | |
public boolean onTouchEvent(MotionEvent event) { | |
// handle touch events here | |
mOnTouchEventWorkingArray[0] = event.getX(); | |
mOnTouchEventWorkingArray[1] = event.getY(); | |
mOnTouchEventWorkingArray = scaledPointsToScreenPoints(mOnTouchEventWorkingArray); | |
event.setLocation(mOnTouchEventWorkingArray[0], mOnTouchEventWorkingArray[1]); | |
switch (event.getAction() & MotionEvent.ACTION_MASK) { | |
case MotionEvent.ACTION_DOWN: | |
savedMatrix.set(matrix); | |
start.set(event.getX(), event.getY()); | |
mode = ZOOM; | |
lastEvent = null; | |
break; | |
case MotionEvent.ACTION_POINTER_DOWN: | |
oldDist = spacing(event); | |
if (oldDist > 10f) { | |
savedMatrix.set(matrix); | |
midPoint(mid, event); | |
mode = ZOOM; | |
} | |
lastEvent = new float[4]; | |
lastEvent[0] = event.getX(0); | |
lastEvent[1] = event.getX(1); | |
lastEvent[2] = event.getY(0); | |
lastEvent[3] = event.getY(1); | |
break; | |
case MotionEvent.ACTION_UP: | |
case MotionEvent.ACTION_POINTER_UP: | |
mode = NONE; | |
lastEvent = null; | |
break; | |
case MotionEvent.ACTION_MOVE: | |
if (mode == ZOOM) { | |
matrix.set(savedMatrix); | |
float newDist = spacing(event); | |
if (newDist > 10f) { | |
matrix.set(savedMatrix); | |
float scale = (newDist / oldDist); | |
float[] values = new float[9]; | |
matrix.getValues(values); | |
// limit scale matrix value | |
float totalZoomedScale = scale * values[Matrix.MSCALE_X]; | |
if(totalZoomedScale >= MAX_ZOOM) { | |
scale = MAX_ZOOM / values[Matrix.MSCALE_X]; | |
} else if (totalZoomedScale <= MIN_ZOOM) { | |
scale = MIN_ZOOM / values[Matrix.MSCALE_X]; | |
} | |
matrix.postScale(scale, scale, mid.x, mid.y); | |
matrix.invert(matrixInverse); | |
} | |
} | |
break; | |
} | |
invalidate(); | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment