How to implement pinch and pan zoom on surface view ?

I spent lots of time to find the way, how to  implement zoom functionality on surface view, because in case of  image view you can implement it very easily by using matrix, but its implementation on surface view was not easy. I am going to share that particular code, may be it can helpful for anyone at anytime :).

// AndroidSurfaceViewUI Activity
 package com.example.zoom;

import android.app.Activity;
import android.os.Bundle;
import android.view.ViewGroup;

public class AndroidSurfaceViewUI extends Activity {
ZoomView view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view = new ZoomView(this);
view.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));

setContentView(view);

}
}

// Surface View
package com.example.zoom;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.SurfaceView;
import android.view.View;

public class ZoomView extends SurfaceView {

private static final int INVALID_POINTER_ID = -1;
public Bitmap mMyChracter;
private float mPosX;
private float mPosY;

private float mLastTouchX;
private float mLastTouchY;
private int mActivePointerId = INVALID_POINTER_ID;

View currentView;

private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;

private float focusX;
private float focusY;

private float lastFocusX = -1;
private float lastFocusY = -1;

static final int IMG_WIDTH = 640;
static final int IMG_HEIGHT = 480;

static final int IMAGE_X_POS = 560;
static final int IMAGE_Y_POS = 20;

Matrix matrix;
float sy;
float sx;
public static Context context;
public ZoomView(Context context) {
this(context, null, 0);
mMyChracter = BitmapFactory.decodeResource(context.getResources(), R.drawable.scan_off);

setWillNotDraw(false);
matrix = new Matrix();
final int width = mMyChracter.getWidth();
final int height = mMyChracter.getHeight();
sx = 550 / (float) width;
sy = 700 / (float) height;
matrix.setScale(sx, sy);
matrix.postTranslate(sx + IMAGE_X_POS, sy + IMAGE_Y_POS);
mMyChracter = Bitmap.createScaledBitmap(mMyChracter, height, width,
true);
currentView = this;
this.context = context;

}

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

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

mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
mScaleDetector.onTouchEvent(ev);

final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {

final float x = ev.getX() / mScaleFactor;
final float y = ev.getY() / mScaleFactor;
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);

break;
}

case MotionEvent.ACTION_MOVE: {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex) / mScaleFactor;
final float y = ev.getY(pointerIndex) / mScaleFactor;

// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {

final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
mPosX += dx;
mPosY += dy;

invalidate();
}

mLastTouchX = x;
mLastTouchY = y;

break;
}

case MotionEvent.ACTION_UP: {
mActivePointerId = INVALID_POINTER_ID;
break;
}

case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}

case MotionEvent.ACTION_POINTER_UP: {

final int pointerIndex = (ev.getAction() &    MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = ev.getX(newPointerIndex) / mScaleFactor;
mLastTouchY = ev.getY(newPointerIndex) / mScaleFactor;
mActivePointerId = ev.getPointerId(newPointerIndex);
}
break;
}
}

return true;
}

@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);

setBackgroundColor(Color.WHITE);
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor, focusX, focusY);
canvas.translate(mPosX, mPosY);
canvas.drawBitmap(mMyChracter, matrix, null);
canvas.restore();
}

private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {

// float x = detector.getFocusX();
// float y = detector.getFocusY();

lastFocusX = -1;
lastFocusY = -1;

return true;
}

@Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();

focusX = detector.getFocusX();
focusY = detector.getFocusY();

if (lastFocusX == -1)
lastFocusX = focusX;
if (lastFocusY == -1)
lastFocusY = focusY;

mPosX += (focusX - lastFocusX);
mPosY += (focusY - lastFocusY);
Log.v("Hi Zoom", "Factor:"  + mScaleFactor);
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.5f, Math.min(mScaleFactor, 2.0f));

lastFocusX = focusX;
lastFocusY = focusY;

invalidate();
return true;
}

}
}

May be this code contains some project dependent attribute, because i am sharing only zoom related lines of code.

Thanks

Comments

  1. How to use other background for this view? You are hard code for example. You should create example for general case.

    And pinch zoom is not in this code

    ReplyDelete
  2. Hi, It was very helpful for me. I had the same problem of zoom and pan in a canvas. I now was a different issue, if possible please help me in solving it.

    I have to actually write over the image with zoom pan etc. I have added my changes in the onDraw Function.

    During the zoom is not applied, the gesture is mapped at the correct place. but when i zoom and starting annotating with my finger, the gestures is moved some 100pixels aways from my finger.

    Can u please guide me ?

    ReplyDelete
  3. Hi, It is working fine. thnks

    ReplyDelete
  4. Thank you! it was very very very helpful for me..

    ReplyDelete
  5. Thank you! it was very very very helpful for me..

    ReplyDelete
  6. Hi, Thank u for your help.please help me how to draw over image like annotate by freehand. i need for this to develop this.

    ReplyDelete
  7. i thin'k you should add event touch event when move image on screeen.
    you code below . http://tienanh-it.blogspot.com/2017/10/implement-pinch-to-zoom-with.html

    ReplyDelete
  8. what is R.drawable.scan_off

    ReplyDelete

Post a Comment

Popular posts from this blog

Android InApp Billing / Payment

Zooming and Dragging images using matrix in android