I Have a Frame Layout having 4 images in it image1,image2,image3,image4. I am able to zoom/pinch this Frame layout.I am able to perform on touch event for images by using X,Y position without zoom.
Question : How to achieve images on touch functionality individually after zoom.
public class ZoomLayout extends FrameLayout implements caleGestureDetector.OnScaleGestureListener {
private enum Mode {
NONE,
DRAG,
ZOOM
}
private static final String TAG = "ZoomLayout";
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 4.0f;
private Mode mode = Mode.NONE;
private float scale = 1.0f;
private float lastScaleFactor = 0f;
// Where the finger first touches the screen
private float startX = 0f;
private float startY = 0f;
// How much to translate the canvas
private float dx = 0f;
private float dy = 0f;
private float prevDx = 0f;
private float prevDy = 0f;
public ZoomLayout(Context context) {
super(context);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
#SuppressLint("NewApi")
private void init(final Context context) {
final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, this);
this.setOnTouchListener(new View.OnTouchListener() {
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
#SuppressLint("NewApi")
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
com.example.target.ZoomLayout test=(com.example.target.ZoomLayout) view;
System.out.println( "iddd : "+test.getId());
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "DOWN");
if (scale > MIN_ZOOM) {
mode = Mode.DRAG;
startX = motionEvent.getX() - prevDx;
startY = motionEvent.getY() - prevDy;
}
FrameLayout vv=(FrameLayout) child();
System.out.println("count= "+vv.getChildCount());
int count=vv.getChildCount();
for (int i = 1; i <count; i++) {
float startx,starty,endx,endy;
ImageView iv=(ImageView) vv.getChildAt(i);
startx=iv.getX()+startX;
starty=iv.getY()+startY;
endx=iv.getX()+iv.getWidth();
endy=iv.getY()+iv.getHeight();
if(motionEvent.getX()>( startx ) && motionEvent.getX()<(endx ) && motionEvent.getY()>(( starty )+getStatusBarHeight() ) && motionEvent.getY()<(( endy)+getStatusBarHeight()))
{
System.out.println("count= "+iv.getId());
Toast.makeText(context, "i = "+i, 3000).show();
}
}
break;
case MotionEvent.ACTION_MOVE:
if (mode == Mode.DRAG) {
dx = motionEvent.getX() - startX;
dy = motionEvent.getY() - startY;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = Mode.ZOOM;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = Mode.DRAG;
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "UP");
mode = Mode.NONE;
prevDx = dx;
prevDy = dy;
break;
}
scaleDetector.onTouchEvent(motionEvent);
if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
getParent().requestDisallowInterceptTouchEvent(true);
float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
float maxDy = (child().getHeight() - (child().getHeight() / scale))/ 2 * scale;
dx = Math.min(Math.max(dx, -maxDx), maxDx);
dy = Math.min(Math.max(dy, -maxDy), maxDy);
Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
+ ", max " + maxDx);
applyScaleAndTranslation();
}
return true;
}
});
}
// ScaleGestureDetector
#Override
public boolean onScaleBegin(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleBegin");
return true;
}
#Override
public boolean onScale(ScaleGestureDetector scaleDetector) {
float scaleFactor = scaleDetector.getScaleFactor();
Log.i(TAG, "onScale" + scaleFactor);
if (lastScaleFactor == 0 || (Math.signum(scaleFactor) == Math.signum(lastScaleFactor))) {
scale *= scaleFactor;
scale = Math.max(MIN_ZOOM, Math.min(scale, MAX_ZOOM));
lastScaleFactor = scaleFactor;
} else {
lastScaleFactor = 0;
}
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleEnd");
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
#SuppressLint("NewApi")
private void applyScaleAndTranslation() {
child().setScaleX(scale);
child().setScaleY(scale);
child().setTranslationX(dx);
child().setTranslationY(dy);
}
private View child() {
return getChildAt(0);
}
public float getStatusBarHeight() {
float result = 0;
float resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize((int) resourceId);
}
return result;
}
}
public class MainActivity extends ActionBarActivity {
com.example.target.ZoomLayout zoomerframe;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
zoomerframe=(com.example.target.ZoomLayout)findViewById(R.id.zoomerframe);
ImageView iv=new ImageView(getApplicationContext());
LayoutParams params=new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
FrameLayout frame=new FrameLayout(getApplicationContext());
iv.setLayoutParams(params);
iv.setLayoutParams(params);
iv.setImageResource(R.drawable.notifications_bg);
frame.addView(iv);
params = new FrameLayout.LayoutParams(
200,
200);
params.leftMargin = (int) 100;
params.topMargin = (int) 100;
ImageView iv2=new ImageView(getApplicationContext());
iv2.setLayoutParams(params);
iv2.setImageResource(R.drawable.trnspr);
frame.addView(iv2);
params = new FrameLayout.LayoutParams(
50,
50);
params.leftMargin = (int) 500;
params.topMargin = (int) 500;
ImageView iv3=new ImageView(getApplicationContext());
iv3.setLayoutParams(params);
iv3.setImageResource(R.drawable.trnspr);
frame.addView(iv3);
zoomerframe.addView(frame);
}
}
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context="com.example.target.MainActivity"
tools:ignore="MergeRootFrame">
<com.example.target.ZoomLayout
android:id="#+id/zoomerframe"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_centerInParent="true"
></com.example.target.ZoomLayout >
</RelativeLayout>
Related
I'm trying to implement pinch zoom on a SurfaceView. I've done a lot of research regarding this and I found this class to implement pinch zoom. Here is how I modified it:
public class ZoomLayout extends FrameLayout implements ScaleGestureDetector.OnScaleGestureListener {
private SurfaceView mSurfaceView;
private enum Mode {
NONE,
DRAG,
ZOOM
}
private static final String TAG = "ZoomLayout";
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 4.0f;
private Mode mode;
private float scale = 1.0f;
private float lastScaleFactor = 0f;
// Where the finger first touches the screen
private float startX = 0f;
private float startY = 0f;
// How much to translate the canvas
private float dx = 0f;
private float dy = 0f;
private float prevDx = 0f;
private float prevDy = 0f;
public ZoomLayout(Context context) {
super(context);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, this);
setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View view, MotionEvent motionEvent) {
ZoomLayout.this.mSurfaceView = (SurfaceView) view.findViewById(R.id.mSurfaceView);
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "DOWN");
if (scale > MIN_ZOOM) {
mode = Mode.DRAG;
startX = motionEvent.getX() - prevDx;
startY = motionEvent.getY() - prevDy;
}
break;
case MotionEvent.ACTION_MOVE:
if (mode == Mode.DRAG) {
dx = motionEvent.getX() - startX;
dy = motionEvent.getY() - startY;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = Mode.ZOOM;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = Mode.NONE;
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "UP");
mode = Mode.NONE;
prevDx = dx;
prevDy = dy;
break;
}
scaleDetector.onTouchEvent(motionEvent);
if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
getParent().requestDisallowInterceptTouchEvent(true);
float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
float maxDy = (child().getHeight() - (child().getHeight() / scale))/ 2 * scale;
dx = Math.min(Math.max(dx, -maxDx), maxDx);
dy = Math.min(Math.max(dy, -maxDy), maxDy);
Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
+ ", max " + maxDx);
applyScaleAndTranslation();
}
return true;
}
});
}
// ScaleGestureDetector
public boolean onScaleBegin(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleBegin");
return true;
}
public boolean onScale(ScaleGestureDetector scaleDetector) {
float scaleFactor = scaleDetector.getScaleFactor();
Log.i(TAG, "mode:" + this.mode + ", onScale:" + scaleFactor);
if (this.lastScaleFactor == 0.0f || Math.signum(scaleFactor) == Math.signum(this.lastScaleFactor)) {
this.scale *= scaleFactor;
this.scale = Math.max(MIN_ZOOM, Math.min(this.scale, MAX_ZOOM));
this.lastScaleFactor = scaleFactor;
} else {
this.lastScaleFactor = 0.0f;
}
if (this.mSurfaceView != null) {
int orgWidth = getWidth();
int _width = (int) (((float) orgWidth) * this.scale);
int _height = (int) (((float) getHeight()) * this.scale);
LayoutParams params = (LayoutParams) this.mSurfaceView.getLayoutParams();
params.height = _height;
params.width = _width;
this.mSurfaceView.setLayoutParams(params);
child().setScaleX(this.scale);
child().setScaleY(this.scale);
}
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleEnd");
}
private void applyScaleAndTranslation() {
child().setScaleX(scale);
child().setScaleY(scale);
child().setTranslationX(dx);
child().setTranslationY(dy);
}
private View child() {
return getChildAt(0);
}
and I implement it into my layout like this:
<pacageName.control.ZoomLayout
android:id="#+id/mZoomLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:measureAllChildren="true">
<SurfaceView
android:id="#+id/mSurfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</pacageName.control.ZoomLayout>
The problem I'm having is that the zoom is not 'smooth' and the behavior of the zoom is different in different devices (Samsung S4 (Lollipop) and J7Pro (Nougat)).
I'm not sure why pinch zoom is "glitchy" and why it doesn't zoom the same way on the two different devices.
Edit 1: Memory and CPU consumption image added below -
EDIT 2: After trying something else I'm facing a new issue -
I changed my ZoomLayout completely to the following:
public class ZoomableSurfaceView extends SurfaceView {
private ScaleGestureDetector SGD;
private Context context;
private boolean isSingleTouch;
private float width, height = 0;
private float scale = 1f;
private float minScale = 1f;
private float maxScale = 5f;
int left, top, right, bottom;
public ZoomableSurfaceView(Context context) {
super(context);
this.context = context;
init();
}
public ZoomableSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init();
}
public ZoomableSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
init();
}
private void init() {
setOnTouchListener(new MyTouchListeners());
SGD = new ScaleGestureDetector(context, new ScaleListener());
this.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
}
});
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (width == 0 && height == 0) {
width = ZoomableSurfaceView.this.getWidth();
height = ZoomableSurfaceView.this.getHeight();
this.left = left;
this.right = right;
this.top = top;
this.bottom = bottom;
}
}
private class MyTouchListeners implements View.OnTouchListener {
float dX, dY;
MyTouchListeners() {
super();
}
#Override
public boolean onTouch(View view, MotionEvent event) {
SGD.onTouchEvent(event);
if (event.getPointerCount() > 1) {
isSingleTouch = false;
} else {
if (event.getAction() == MotionEvent.ACTION_UP) {
isSingleTouch = true;
}
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
dX = ZoomableSurfaceView.this.getX() - event.getRawX();
dY = ZoomableSurfaceView.this.getY() - event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
if (isSingleTouch) {
ZoomableSurfaceView.this.animate()
.x(event.getRawX() + dX)
.y(event.getRawY() + dY)
.setDuration(0)
.start();
checkDimension(ZoomableSurfaceView.this);
}
break;
default:
return true;
}
return true;
}
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
//Log.e("onGlobalLayout: ", scale + " " + width + " " + height);
scale *= detector.getScaleFactor();
scale = Math.max(minScale, Math.min(scale, maxScale));
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams((int) (width * scale), (int) (height * scale));
Log.e("onGlobalLayout: ", (int) (width * scale) + " " + (int) (height * scale));
ZoomableSurfaceView.this.setLayoutParams(params);
checkDimension(ZoomableSurfaceView.this);
return true;
}
}
private void checkDimension(View vi) {
if (vi.getX() > left) {
vi.animate()
.x(left)
.y(vi.getY())
.setDuration(0)
.start();
}
if ((vi.getWidth() + vi.getX()) < right) {
vi.animate()
.x(right - vi.getWidth())
.y(vi.getY())
.setDuration(0)
.start();
}
if (vi.getY() > top) {
vi.animate()
.x(vi.getX())
.y(top)
.setDuration(0)
.start();
}
if ((vi.getHeight() + vi.getY()) < bottom) {
vi.animate()
.x(vi.getX())
.y(bottom - vi.getHeight())
.setDuration(0)
.start();
}
}
}
The problem I'm facing now is that it zooms to X - 0 and Y - 0 meaning that it zooms to the top-left of the screen instead of zooming to the point between my fingers.. I think it could be related to the following:
#Override
public boolean onScale(ScaleGestureDetector detector) {
//Log.e("onGlobalLayout: ", scale + " " + width + " " + height);
scale *= detector.getScaleFactor();
scale = Math.max(minScale, Math.min(scale, maxScale));
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams((int) (width * scale), (int) (height * scale));
Log.e("onGlobalLayout: ", (int) (width * scale) + " " + (int) (height * scale));
ZoomableSurfaceView.this.setLayoutParams(params);
checkDimension(ZoomableSurfaceView.this);
return true;
}
I have an idea that I should detect the centre point of the 2 fingers and add that to OnScale. How should I proceed?
EDIT 3:
Ok I finally got the zoom working perfectly on my J7 Pro running Nougat. But the problem now, is that my SurfaceView in my S4 running Lollipop doesn't get zoomed in, instead my SurfaceView gets moved to the top left corner. I have tested placing a ImageView inside my custom zoomview and the image gets zoomed perfectly like expected. This is how my custom zoom class looks like now:
public class ZoomLayout extends FrameLayout implements ScaleGestureDetector.OnScaleGestureListener {
private enum Mode {
NONE,
DRAG,
ZOOM
}
private static final String TAG = "ZoomLayout";
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 4.0f;
private Mode mode = Mode.NONE;
private float scale = 1.0f;
private float lastScaleFactor = 0f;
// Where the finger first touches the screen
private float startX = 0f;
private float startY = 0f;
// How much to translate the canvas
private float dx = 0f;
private float dy = 0f;
private float prevDx = 0f;
private float prevDy = 0f;
public ZoomLayout(Context context) {
super(context);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, this);
setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "DOWN");
if (scale > MIN_ZOOM) {
mode = Mode.DRAG;
startX = motionEvent.getX() - prevDx;
startY = motionEvent.getY() - prevDy;
}
break;
case MotionEvent.ACTION_MOVE:
if (mode == Mode.DRAG) {
dx = motionEvent.getX() - startX;
dy = motionEvent.getY() - startY;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = Mode.ZOOM;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = Mode.NONE; // changed from DRAG, was messing up zoom
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "UP");
mode = Mode.NONE;
prevDx = dx;
prevDy = dy;
break;
}
scaleDetector.onTouchEvent(motionEvent);
if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
getParent().requestDisallowInterceptTouchEvent(true);
float maxDx = child().getWidth() * (scale - 1); // adjusted for zero pivot
float maxDy = child().getHeight() * (scale - 1); // adjusted for zero pivot
dx = Math.min(Math.max(dx, -maxDx), 0); // adjusted for zero pivot
dy = Math.min(Math.max(dy, -maxDy), 0); // adjusted for zero pivot
Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
+ ", max " + maxDx);
applyScaleAndTranslation();
}
return true;
}
});
}
// ScaleGestureDetector
#Override
public boolean onScaleBegin(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleBegin");
return true;
}
#Override
public boolean onScale(ScaleGestureDetector scaleDetector) {
float scaleFactor = scaleDetector.getScaleFactor();
Log.i(TAG, "onScale(), scaleFactor = " + scaleFactor);
if (lastScaleFactor == 0 || (Math.signum(scaleFactor) == Math.signum(lastScaleFactor))) {
float prevScale = scale;
scale *= scaleFactor;
scale = Math.max(MIN_ZOOM, Math.min(scale, MAX_ZOOM));
lastScaleFactor = scaleFactor;
float adjustedScaleFactor = scale / prevScale;
// added logic to adjust dx and dy for pinch/zoom pivot point
Log.d(TAG, "onScale, adjustedScaleFactor = " + adjustedScaleFactor);
Log.d(TAG, "onScale, BEFORE dx/dy = " + dx + "/" + dy);
float focusX = scaleDetector.getFocusX();
float focusY = scaleDetector.getFocusY();
Log.d(TAG, "onScale, focusX/focusy = " + focusX + "/" + focusY);
dx += (dx - focusX) * (adjustedScaleFactor - 1);
dy += (dy - focusY) * (adjustedScaleFactor - 1);
Log.d(TAG, "onScale, dx/dy = " + dx + "/" + dy);
} else {
lastScaleFactor = 0;
}
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleEnd");
}
private void applyScaleAndTranslation() {
child().setScaleX(scale);
child().setScaleY(scale);
child().setPivotX(0f); // default is to pivot at view center
child().setPivotY(0f); // default is to pivot at view center
child().setTranslationX(dx);
child().setTranslationY(dy);
}
private View child() {
return getChildAt(0);
}
Edit 4: Added video of behaviour:
Please see how the current zoom looks like after updating my zoomLayout class to edit 3 -
Video of SurfaceView zoom behaviour
Instead of using SurfaceView, use TextureView which has more features and is preferred over SurfaceView. See TextureView Documentation
Your code is indeed right and should work but in some cases, The MediaPlayer drawn on the SurfaceView does not resize and makes an illusion of the zoom happening at the position 0,0 of the phone. If you set a size smaller than the parent for the SurfaceView and set a background for the parent, you will notice this
SOLUTION IN CODE
Sample Project: Pinch Zoom
The project extends the TextureView and implements the touch to create the zoom effect.
IMAGE: DEMONSTRATION
scaling SurfaceView by scaling its parent FrameLayout will work only in Nougat (API 24) and above
this scaling does not change the SurfaceView resolution i.e. it is much safer and smooth than scaling SurfaceView by changing its LayoutParams (but resolution is fixed). This is the only option for custom view e.g. GStreamerSurfaceView
I have the following which allows me to zoom in and out indefinitely, however I am trying to set a min and max value for zoom so that the layout is not too large or too small. I have tried to use but I just don't understand where to place this in the code.
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = //set Maximum zooming level
float scale = Math.max(MIN_ZOOM, Math.min(scale, MAX_ZOOM));
public class ZoomableView extends RelativeLayout {
// States.
private static final byte NONE = 0;
private static final byte DRAG = 1;
private static final byte ZOOM = 2;
private byte mode = NONE;
// Matrices used to move and zoom image.
private Matrix matrix = new Matrix();
private Matrix matrixInverse = new Matrix();
private Matrix savedMatrix = new Matrix();
// Parameters for zooming.
private PointF start = new PointF();
private PointF mid = new PointF();
private float oldDist = 1f;
private float[] lastEvent = null;
private long lastDownTime = 0l;
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);
}
public ZoomableView(Context context) {
super(context);
init(context);
}
public ZoomableView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomableView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
}
/**
* Determine the space between the first two fingers
*/
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
/**
* 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 float[] scaledPointsToScreenPoints(float[] a) {
matrix.mapPoints(a);
return a;
}
private float[] screenPointsToScaledPoints(float[] a) {
matrixInverse.mapPoints(a);
return a;
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
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) {
float[] values = new float[9];
matrix.getValues(values);
canvas.save();
canvas.translate(values[Matrix.MTRANS_X], values[Matrix.MTRANS_Y]);
canvas.scale(values[Matrix.MSCALE_X], values[Matrix.MSCALE_Y]);
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);
mode = DRAG;
lastEvent = null;
long downTime = event.getDownTime();
if (downTime - lastDownTime < 300l) {
float density = getResources().getDisplayMetrics().density;
if (Math.max(Math.abs(start.x - event.getX()), Math.abs(start.y - event.getY())) < 40.f * density) {
savedMatrix.set(matrix);
mid.set(event.getX(), event.getY());
mode = ZOOM;
lastEvent = new float[4];
lastEvent[0] = lastEvent[1] = event.getX();
lastEvent[2] = lastEvent[3] = event.getY();
}
lastDownTime = 0l;
} else {
lastDownTime = downTime;
}
start.set(event.getX(), event.getY());
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:
final float density = getResources().getDisplayMetrics().density;
if (mode == DRAG) {
matrix.set(savedMatrix);
float dx = event.getX() - start.x;
float dy = event.getY() - start.y;
matrix.postTranslate(dx, dy);
matrix.invert(matrixInverse);
if (Math.max(Math.abs(start.x - event.getX()), Math.abs(start.y - event.getY())) > 20.f * density) {
lastDownTime = 0l;
}
} else if (mode == ZOOM) {
if (event.getPointerCount() > 1) {
float newDist = spacing(event);
if (newDist > 10f * density) {
matrix.set(savedMatrix);
float scale = (newDist / oldDist);
matrix.postScale(scale, scale, mid.x, mid.y);
matrix.invert(matrixInverse);
}
} else {
matrix.set(savedMatrix);
float scale = event.getY() / start.y;
matrix.postScale(scale, scale, mid.x, mid.y);
matrix.invert(matrixInverse);
}
}
break;
}
invalidate();
return true;
}
}
You need to make a class extending RelativeLayout , there you can set it by overriding the getMaxZoomLevel() and getMinZoomLevel(), like this:
public class ZoomableView extends RelativeLayout {
#Override
public int getMaxZoomLevel() {
return 22;
}
#Override
public int getMinZoomLevel() {
return 12;
}
}
I have a view-group (ZoomableViewGroup is my custom class extending ViewGroup) in my layout as parent, which inside contains a relative layout. The width and height of ViewGroup is by default 2286 height and 1600 width which is larger than device size. How to set the ViewGroup size to screen size ? My final expected result is, I am trying to zoom and pan the relative out, it moves out of screen due to the huge size of ViewGroup.
<com.example.pdoc.zoomablepdf.ZoomableViewGroup
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/zoomLayout"
xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="#drawable/samplebg"
android:id="#+id/RelLayout">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="#+id/LinLayout">
</LinearLayout>
</RelativeLayout>
</com.example.pdoc.zoomablepdf.ZoomableViewGroup>
Java code
ZoomableViewGroup mZoomableViewGroup;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mainnew);
mZoomableViewGroup = (ZoomableViewGroup) findViewById(R.id.zoomLayout);
LinearLayout ll = (LinearLayout)findViewById(R.id.LinLayout);
//add edittext dynamically
EditText myText = new EditText(MainnewActivity.this);
myText.setText("Edit Me");
LinearLayout.LayoutParams lp = new
LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
lp.gravity= Gravity.CENTER_HORIZONTAL;
ll.addView(myText, lp);
//add checkbox dynamically
CheckBox myChk = new CheckBox(MainnewActivity.this);
myChk.setText("Check me");
LinearLayout.LayoutParams chklp = new
LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
chklp.gravity= Gravity.CENTER_HORIZONTAL;
ll.addView(myChk, chklp);
//add imageview dynamically
ImageView myImg = new ImageView(MainnewActivity.this);
myImg.setImageResource(R.drawable.apple);
LinearLayout.LayoutParams imgLP = new
LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
imgLP.gravity= Gravity.CENTER_HORIZONTAL;
ll.addView(myImg, imgLP);
//add button dynamically
Button mybtn1 = new Button(MainnewActivity.this);
mybtn1.setText("Click me");
LinearLayout.LayoutParams btnLP = new
LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
btnLP.gravity= Gravity.CENTER_HORIZONTAL;
ll.addView(mybtn1, btnLP);
mybtn1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(MainnewActivity.this, "6--"+ mZoomableViewGroup.getHeight() + "_" + mZoomableViewGroup.getWidth(),Toast.LENGTH_SHORT).show();
}
});
}
ZoomableViewGroup class code
public class ZoomableViewGroup extends ViewGroup {
// States.
private static final byte NONE = 0;
private static final byte DRAG = 1;
private static final byte ZOOM = 2;
private static final byte DTAP = 3;
private byte mode = NONE;
// Matrices used to move and zoom image.
private Matrix matrix = new Matrix();
private Matrix matrixInverse = new Matrix();
private Matrix savedMatrix = new Matrix();
// Parameters for zooming.
private PointF start = new PointF();
private PointF mid = new PointF();
private float oldDist = 1f;
private float[] lastEvent = null;
private long lastDownTime = 0l;
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);
}
public ZoomableViewGroup(Context context) {
super(context);
Toast.makeText(context, "c1-" + this.getHeight() + "_" + this.getWidth(), Toast.LENGTH_SHORT).show ();
init(context);
}
public ZoomableViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
Toast.makeText(context, "c2-" + this.getHeight() + "_" + this.getWidth(), Toast.LENGTH_SHORT).show ();
init(context);
}
public ZoomableViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Toast.makeText(context, "c3-" + this.getHeight() + "_" + this.getWidth(), Toast.LENGTH_SHORT).show ();
init(context);
}
private void init(Context context) {
}
//Determine the space between the first two fingers
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
//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 float[] scaledPointsToScreenPoints(float[] a) {
matrix.mapPoints(a);
return a;
}
private float[] screenPointsToScaledPoints(float[] a) {
matrixInverse.mapPoints(a);
return a;
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
//super.onLayout(changed, left, top, right, bottom);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
child.layout(child.getLeft(), child.getTop(), child.getLeft()+child.getMeasuredWidth(), child.getTop()+child.getMeasuredHeight());
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//measure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
//measure(widthMeasureSpec, heightMeasureSpec);
}
#Override
protected void dispatchDraw(Canvas canvas) {
float[] values = new float[9];
matrix.getValues(values);
canvas.save();
canvas.translate(values[Matrix.MTRANS_X], values[Matrix.MTRANS_Y]);
canvas.scale(values[Matrix.MSCALE_X], values[Matrix.MSCALE_Y]);
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);
mode = DRAG;
lastEvent = null;
long downTime = event.getDownTime();
if (downTime - lastDownTime < 300l) {
float density = getResources().getDisplayMetrics().density;
if (Math.max(Math.abs(start.x - event.getX()), Math.abs(start.y - event.getY())) < 20.f * density) {
savedMatrix.set(matrix);
mid.set(event.getX(), event.getY());
mode = ZOOM;
lastEvent = new float[4];
lastEvent[0] = lastEvent[1] = event.getX();
lastEvent[2] = lastEvent[3] = event.getY();
}
lastDownTime = 0l;
} else {
lastDownTime = downTime;
}
start.set(event.getX(), event.getY());
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:
final float density = getResources().getDisplayMetrics().density;
if (mode == DRAG) {
matrix.set(savedMatrix);
float dx = event.getX() - start.x;
float dy = event.getY() - start.y;
RelativeLayout Rel = (RelativeLayout)findViewById(R.id.RelLayout);
//getGlobalVisibleRect(Rel);
ZoomableViewGroup ZVG = (ZoomableViewGroup) findViewById(R.id.zoomLayout);
Rel.getTop();
Rel.getY();
//if(dx > 0) dx=0;
//if(dy > 0) dy=0;
//Toast.makeText(getContext(), dx + "_" + dy + "REL=" + Rel.getTop()+"_"+ Rel.getLeft(), Toast.LENGTH_SHORT).show ();
//Toast.makeText(getContext(), ZVG.getHeight() + "-" + ZVG.getWidth(), Toast.LENGTH_SHORT).show ();
matrix.postTranslate(dx, dy);
matrix.invert(matrixInverse);
if (Math.max(Math.abs(start.x - event.getX()), Math.abs(start.y - event.getY())) > 20.f * density) {
lastDownTime = 0l;
}
} else if (mode == ZOOM) {
if (event.getPointerCount() > 1) {
float newDist = spacing(event);
if (newDist > 10f * density) {
matrix.set(savedMatrix);
float scale = (newDist / oldDist);
//to restrict the min zoom not tobe less than screen size
float[] values = new float[9];
matrix.getValues(values);
if(scale*values[matrix.MSCALE_X] <= 1)
{ scale = 1/values[matrix.MSCALE_X]; }
//till here
//condition to check maximum zoom scale to 10
if(scale * values[matrix.MSCALE_X] <10)
matrix.postScale(scale, scale, mid.x, mid.y);
matrix.invert(matrixInverse);
}
} else {
matrix.set(savedMatrix);
float scale = event.getY() / start.y;
//to restrict the min zoom to 1 or not less than screen size
float[] values = new float[9];
matrix.getValues(values);
if(scale*values[matrix.MSCALE_Y] <= 1)
{ scale = 1/values[matrix.MSCALE_Y]; }
//till here
//condition to check maximum zoom scale to 10
if(scale * values[matrix.MSCALE_Y] <10)
matrix.postScale(scale, scale, mid.x, mid.y);
matrix.invert(matrixInverse);
}
}
break;
}
invalidate();
return true;
}
}
Hope this helps . Try to Set the height and width of viewgroup dynamically.
Display display = getWindowManager().getDefaultDisplay();
ZoomableViewGroup layout= (ZoomableViewGroup) findViewById(R.id.zoomLayout);
int width=display.getWidth();
int height=display.getHeight();
FrameLayout.LayoutParams parms = new FrameLayout.LayoutParams(width,height);
layout.setLayoutParams(parms);
i am working on this android project, which has this typical UI like 3D chess board grid. Which has Zoom Pan/drag feature on this screen.
I have implemented this functionality by creating custom frame layout class.
Snippet :
public class ZoomLayout extends FrameLayout implements ScaleGestureDetector.OnScaleGestureListener {
float x1=0,y1=0,x2,y2;
ScaleGestureDetector scaleDetector;
Context mContext;
private enum Mode {
NONE,
DRAG,
ZOOM
}
private static final String TAG = "ZoomLayout";
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 6.0f;
private Mode mode = Mode.NONE;
private float scale = 1.0f;
private float lastScaleFactor = 0f;
// Where the finger first touches the screen
private float startX = 0f;
private float startY = 0f;
// How much to translate the canvas
private float dx = 0f;
private float dy = 0f;
private float prevDx = 0f;
private float prevDy = 0f;
ApiCommunicationListener apiListener;
Activity mActivity;
public ZoomLayout(Context context, ApiCommunicationListener apiListener, Activity activity) {
super(context);
this.apiListener = apiListener;
this.mActivity = activity;
init(context);
}
public ZoomLayout(Context context) {
super(context);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
mContext = context;
scaleDetector = new ScaleGestureDetector(context, this);
}
#Override
public boolean onTouchEvent(final MotionEvent motionEvent) {
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
x1 = motionEvent.getX();
y1 = motionEvent.getY();
// System.out.println("TOUCH DOWN : X:" + x1 + " Y:" + y1);
if (scale > MIN_ZOOM) {
mode = Mode.DRAG;
startX = motionEvent.getX() - prevDx;
startY = motionEvent.getY() - prevDy;
}
break;
case MotionEvent.ACTION_MOVE:
if (mode == Mode.DRAG) {
dx = motionEvent.getX() - startX;
dy = motionEvent.getY() - startY;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = Mode.ZOOM;
break;
case MotionEvent.ACTION_POINTER_UP:
//mode = Mode.DRAG;
break;
case MotionEvent.ACTION_UP:
x2 = motionEvent.getX();
y2 = motionEvent.getY();
// System.out.println("TOUCH UP : X:" + x2 + " Y:" + y2);
mode = Mode.NONE;
prevDx = dx;
prevDy = dy;
if (x1 == x2 && y1 == y2 && ApocketApp.mViewClicked != null) {
Utilities.sysOut("-------------In ZoomLayout");
if (ApocketApp.mViewClicked.getTag().toString().contains("A"))
apiListener.onSuccess("onAvatarClick");
else
apiListener.onSuccess("onBuildingClick");
}
break;
}
scaleDetector.onTouchEvent(motionEvent);
if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
getParent().requestDisallowInterceptTouchEvent(true);
float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
float maxDy = (child().getHeight() - (child().getHeight() / scale)) / 2 * scale;
dx = Math.min(Math.max(dx, -maxDx), maxDx);
dy = Math.min(Math.max(dy, -maxDy), maxDy);
/*Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
+ ", max " + maxDx);*/
applyScaleAndTranslation();
}
return true;
}
#Override
public boolean onScaleBegin(ScaleGestureDetector scaleDetector) {
// Log.i(TAG, "onScaleBegin");
// setLayerType(View.LAYER_TYPE_HARDWARE, null);
return true;
}
#Override
public boolean onScale(ScaleGestureDetector scaleDetector) {
float scaleFactor = scaleDetector.getScaleFactor();
//Log.i(TAG, "onScale" + scaleFactor);
if (lastScaleFactor == 0 || (Math.signum(scaleFactor) == Math.signum(lastScaleFactor))) {
scale *= scaleFactor;
scale = Math.max(MIN_ZOOM, Math.min(scale, MAX_ZOOM));
lastScaleFactor = scaleFactor;
} else {
lastScaleFactor = 0;
}
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector scaleDetector) {
//Log.i(TAG, "onScaleEnd");
// setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
private void applyScaleAndTranslation() {
child().setScaleX(scale);
child().setScaleY(scale);
child().setTranslationX(dx);
child().setTranslationY(dy);
}
private View child() {
return getChildAt(0);
}
public void animateToCentre(final RelativeLayout relativeLayout){
Resources res = mContext.getResources();
TypedValue outValue = new TypedValue();
res.getValue(R.dimen.zoom_centre_level_max, outValue, true);
final float zoomEnd = outValue.getFloat();
ObjectAnimator oaX = new ObjectAnimator();
oaX.setDuration(res.getInteger(R.integer.zoom_centre_duration));
oaX.setFloatValues(new float[]{1.0f, zoomEnd});
oaX.setPropertyName("scaleX");
oaX.setFrameDelay(10);
ObjectAnimator oaY = new ObjectAnimator();
oaY.setDuration(res.getInteger(R.integer.zoom_centre_duration));
oaY.setFloatValues(new float[]{1.0f, zoomEnd});
oaY.setPropertyName("scaleY");
oaY.setFrameDelay(10);
ObjectAnimator oaTy = new ObjectAnimator();
oaTy.setDuration(res.getInteger(R.integer.zoom_centre_duration));
oaTy.setFloatValues(new float[]{0.0f, res.getDimension(R.dimen.v_5) * 3});
oaTy.setPropertyName("translationY");
oaTy.setFrameDelay(10);
AnimatorSet zoomAnimation = new AnimatorSet();
zoomAnimation.playTogether(new ObjectAnimator[]{oaX, oaY, oaTy});
//AnimatorSet zoomAnimation = (AnimatorSet) AnimatorInflater.loadAnimator(mContext, R.anim.zoom_in_out);
zoomAnimation.setTarget(child());
zoomAnimation.setInterpolator(new FastOutLinearInInterpolator());
zoomAnimation.start();
zoomAnimation.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
#Override
public void onAnimationEnd(Animator animation) {
//setLayerType(View.LAYER_TYPE_HARDWARE, null);
scale = zoomEnd;
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
}
}
The zooming works fine,smoothly on hdpi or xhdpi devices but it lags on xxhdpi and xxxhdpi devices.Hi have tried using HardwareAccelaration = true in manifest, but doesn't make much difference.
So please can any body suggest some approach to resolve this issue.
Thanks in advance.
This is my code which performs my pinch zoom. It's working only if I use it in the layout where I don't have any widgets which have implemented onClickListner. I don't know how to make zoom in my layout if I have my fingers over those widgets.
package com.example.cosmin.catanadvice;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.FrameLayout;
/**
* Layout that provides pinch-zooming of content. This view should have exactly one child
* view containing the content.
*/
public class ZoomLayout extends FrameLayout implements ScaleGestureDetector.OnScaleGestureListener {
private enum Mode {
NONE,
DRAG,
ZOOM
}
private static final String TAG = "ZoomLayout";
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 4.0f;
private Mode mode = Mode.NONE;
private float scale = 1.0f;
private float lastScaleFactor = 0f;
// Where the finger first touches the screen
private float startX = 0f;
private float startY = 0f;
// How much to translate the canvas
private float dx = 0f;
private float dy = 0f;
private float prevDx = 0f;
private float prevDy = 0f;
public ZoomLayout(Context context) {
super(context);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, this);
this.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "DOWN");
if (scale > MIN_ZOOM) {
mode = Mode.DRAG;
startX = motionEvent.getX() - prevDx;
startY = motionEvent.getY() - prevDy;
}
break;
case MotionEvent.ACTION_MOVE:
if (mode == Mode.DRAG) {
dx = motionEvent.getX() - startX;
dy = motionEvent.getY() - startY;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = Mode.ZOOM;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = Mode.DRAG;
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "UP");
mode = Mode.NONE;
prevDx = dx;
prevDy = dy;
break;
}
scaleDetector.onTouchEvent(motionEvent);
if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
getParent().requestDisallowInterceptTouchEvent(true);
float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
float maxDy = (child().getHeight() - (child().getHeight() / scale))/ 2 * scale;
dx = Math.min(Math.max(dx, -maxDx), maxDx);
dy = Math.min(Math.max(dy, -maxDy), maxDy);
Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
+ ", max " + maxDx);
applyScaleAndTranslation();
}
return true;
}
});
}
// ScaleGestureDetector
#Override
public boolean onScaleBegin(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleBegin");
return true;
}
#Override
public boolean onScale(ScaleGestureDetector scaleDetector) {
float scaleFactor = scaleDetector.getScaleFactor();
Log.i(TAG, "onScale" + scaleFactor);
if (lastScaleFactor == 0 || (Math.signum(scaleFactor) == Math.signum(lastScaleFactor))) {
scale *= scaleFactor;
scale = Math.max(MIN_ZOOM, Math.min(scale, MAX_ZOOM));
lastScaleFactor = scaleFactor;
} else {
lastScaleFactor = 0;
}
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleEnd");
}
private void applyScaleAndTranslation() {
child().setScaleX(scale);
child().setScaleY(scale);
child().setTranslationX(dx);
child().setTranslationY(dy);
}
private View child() {
return getChildAt(0);
}
}
You can use one of the 3rd party libraries which already implemented this one, supporting variety of gestures:
https://github.com/chrisbanes/PhotoView