Dec 042011
 

I was trying to get pinch-zoom and panning working on an Android view today. Basically I was trying to implement the same behavior you see when you use Google Maps (for example). You can zoom in and pan around until the edge of the image, but no further. Also, if the image is fully zoomed out, you can’t pan the image. Implementing the pinch-zoom functionality was pretty easy. I found an example on StackOverflow. I then wanted to implement panning (or dragging) as well. However, I wasn’t able to easily find examples and tutorials for this functionality. I started with this example that comes from the third edition of the Hello, Android! book but I didn’t get too far. So I started playing around a little bit with the events and started writing some code from scratch (using the example from Hello, Android!) so that I could have a better idea of what was happening.

As I mentioned before, getting zoom to work was pretty easy. Implementing panning/dragging was the hard part. The major issues I encountered and subsequently fixed were the following:

  1. Panning continues indefinitely in all directions.
  2. When you zoom and then pan, stop, and then start again, the view jerks to a new position instead of panning from the existing position.
  3. Excessive panning towards the left and top can be constrained, but panning towards the right and bottom is not so easily constrained.

Once I fixed all the problems, I figured that it would be nice to document it for future reference, and I also think it would be a useful resource for others who have the same problem. Now a little disclaimer before I go any further: I’m not an Android expert and I’m really not that great with graphics; I just started it learning to program for Android this semester for one of my Masters electives. So there might be a better way of doing all this, and if there is, please let me know! Also, if you want to skip all the explanations and just see the code, you can skip to the last page.

Let’s start with the simple stuff first, that is implementing pinch-zoom. To implement pinch-zoom, we make use of the ScaleGestureDetector class. This class helps you detect the pinch-zoom event. Using it is pretty simple:

public class ZoomView extends View {

    private static float MIN_ZOOM = 1f;
    private static float MAX_ZOOM = 5f;

    private float scaleFactor = 1.f;
    private ScaleGestureDetector detector;

    public ZoomView(Context context) {
        super(context);
        detector = new ScaleGestureDetector(getContext(), new ScaleListener());
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        detector.onTouchEvent(event);
        return true;
    }

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

        canvas.save();
        canvas.scale(scaleFactor, scaleFactor);

        // ...
        // your canvas-drawing code
        // ...

        canvas.restore();
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            scaleFactor *= detector.getScaleFactor();
            scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));
            invalidate();
            return true;
        }
    }
}

Your view class has four private members: MIN_ZOOM, MAX_ZOOM, detector, and scaleFactor. The first two are static constants that define the maximum and minimum zoom allowed. The third is of type ScaleGestureDetector and does all the heavy lifting as far as zooming is concerned. The fourth member holds the scaling factor i.e., a number that represents the amount of “zoom”.

Now what does this do?

In the constructor, you initialize the detector. The constructor to ScaleGestureDetector takes two parameters: the current context, and a listener. Our listener is defined inside the class ScaleListener which extends the abstract class ScaleGestureDetector.SimpleOnScaleGestureListener. Inside the onScale method, we get the current scale factor from the detector. We then check to see if it is greater or smaller than our upper and lower bounds. If so, we make sure that it we limit the value to be between those bounds. We then call invalidate() which forces the canvas to redraw itself.

The actual scaling happens inside the onDraw method. There, we save the canvas, set its scaling factor (which is the one we got from the detector), draw anything we need to draw, and then restore the canvas. What happens now is that anything you draw on the canvas is now scaled by the scaling factor. This is what gives you the “zoom” effect.

  43 Responses to “Implementing pinch-zoom and pan/drag in an Android view on the canvas”

  1. Thank you for the great tutorial, you helped me greatly figuring out the zooming part!
    I do have one problem though, I cant seem to get the panning to work. I believe the canvas is translating but line that I drew on the canvas does not seem to be. I think my mistake is actually the place where im drawing it, but I cant be sure. I was hoping you could help me out with this problem I have, thank you!

    This is my onDraw portion of the code.

    public void onDraw(Canvas canvas)
    {
    super.onDraw(canvas);
    canvas.save();
    canvas.scale(scaleFactor, scaleFactor);

    // My canvas code…
    Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); // Creating a paint object
    p.setColor(Color.CYAN); // Sets the color to red
    canvas.drawPaint(p);
    p.setColor(Color.RED); // Sets the color to red
    p.setStrokeWidth(10);
    canvas.drawLine(100, 100, 120, 120, p); // Drawing the lines
    // My canvas code…

    canvas.save();

    //We’re going to scale the X and Y coordinates by the same amount
    canvas.scale(scaleFactor, scaleFactor);

    //We need to divide by the scale factor here, otherwise we end up with excessive panning based on our zoom level
    //because the translation amount also gets scaled according to how much we’ve zoomed into the canvas.
    canvas.translate(translateX / scaleFactor, translateY / scaleFactor);
    canvas.restore();
    }

  2. I figured it out already, I put the draw codes in the wrong area, it was drawing in the wrong place, I put it after the canvas translate and it worked already, thanks for the tutorial vivin !

  3. Your tutorial is easy to follow and helped me a lot in implementing a nice zoom-and-drag-feature for my drawing application.
    Thanks a lot for your effort, I really appreciate your decision to share your experience with the world.

  4. @Dan: @Roland:

    Glad that you guys found the tutorial helpful!

  5. Hi thanks a lot for this tutorial, i’m 14 and starting to try and develop for android and this has helped me a lot to better understand how i can put this into effect on an app of my own.

  6. @BiggsyStudios:

    Glad this was of help to you! Good luck with your coding!

  7. Thank you for this! But where was displayWidth and displayHeight initialized?

  8. In invalidate()a function that is already predefined within java/eclipse?

  9. Added the following code to get it to compile:

    private boolean dragged = false;
    private float displayWidth;
    private float displayHeight;

    And inside the constructor:
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();

    displayWidth = display.getWidth();
    displayHeight = display.getHeight();

    This code is not tested yet :)

  10. @Richard: Good catch! Thanks, I’ll update the post as soon as I can!

  11. Hi guy!
    One question: how to use this class?
    I need to show in an Activity an image bigger than the screen, and i would like to give to the user the pan and zoom function.
    To add an image usually I use ImageView, with your class what should i do?
    Thanks!

  12. Hi,
    very informative tut i m using ur code but one prob is thr still i can drag image towards left top corner infinitely.

  13. Hi,
    ssorry to bug u guys again but how to keep the image in centre????????

  14. I have the same problem as CV. When zooming it always goes to 0,0. If I change canvas.scale(scaleFactor, scaleFactor); to canvas.scale(scaleFactor, scaleFactor, detector.getx(), detector.gety()); it will zoom to the correct coordinates, but the scrolling/panning is very messed up and trying to compensate for that diff in the left, right, top, bottom calculations doesn’t seem to be working. Granted my math could be wrong. Any ideas?

  15. @cv: That seems strange. Perhaps you aren’t checking the top-left boundary?

    @so, @cv:

    When I zoom it seems to zoom fine (i.e., it doesn’t zoom towards 0,0). How does the scrolling/panning get messed up when you use detector.getX() and detector.getY()?

  16. great article.
    I have a problem, how to zoom at finger click position? thanks

  17. Hi,
    Very nice tutorial.It is very help full and easy to understand. thanks a lot.
    I have one issue it works fine but speed is very slow. how does solve it.
    please help me

  18. Hello. I have two questions:
    1: If I want to know when the surface is clicked/ tapped, will this interfere with the input demonstrated here.
    2: I would like to cut down on how much I would have to draw. It is evading me, but how can I determine the actual area of the canvas that is shown? I am thinking that I can easily use the translateXY values and scale against height and width to determine the box. Just want to be certain.

  19. You can also extends an ImageView instead of a View. In this way you have the SetBitmap function available to zoom an image. You just have to move the code “super.onDraw(canvas);” and put it before the “canvas.restore();”

    Also add this constructor in order to inflate the layout from XML file :

    public ImageZoomView(Context context, AttributeSet attrs)
    {
    super(context, attrs);
    detector = new ScaleGestureDetector(getContext(), new ScaleListener());

    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();

    displayWidth = display.getWidth();
    displayHeight = display.getHeight();
    }

  20. Another bug to correct : put the scale AFTER the translate : matrix transformation is inverted to the function order.

  21. And then in order to translate at a correct speed, add distance *= scaleFactor; in “case MotionEvent.ACTION_MOVE: ”

    if(distance > 0)
    {
    dragged = true;
    distance *= scaleFactor;
    }

    Below is the complete class that perfectly works with ImageView as parent class and with SetBitmap Function

    package IHM;

    import android.content.Context;
    import android.graphics.Canvas;
    import android.util.AttributeSet;
    import android.view.Display;
    import android.view.MotionEvent;
    import android.view.ScaleGestureDetector;
    import android.view.WindowManager;
    import android.widget.ImageView;

    public class ImageZoomView extends ImageView {

    //These two constants specify the minimum and maximum zoom
    private static float MIN_ZOOM = 1f;
    private static float MAX_ZOOM = 5f;

    private float scaleFactor = 1.f;
    private ScaleGestureDetector detector;

    //These constants specify the mode that we’re in
    private static int NONE = 0;
    private static int DRAG = 1;
    private static int ZOOM = 2;

    private int mode;

    //These two variables keep track of the X and Y coordinate of the finger when it first
    //touches the screen
    private float startX = 0f;
    private float startY = 0f;

    //These two variables keep track of the amount we need to translate the canvas along the X
    //and the Y coordinate
    private float translateX = 0f;
    private float translateY = 0f;

    //These two variables keep track of the amount we translated the X and Y coordinates, the last time we
    //panned.
    private float previousTranslateX = 0f;
    private float previousTranslateY = 0f;

    private boolean dragged = false;
    private float displayWidth;
    private float displayHeight;

    public ImageZoomView(Context context, AttributeSet attrs)
    {
    super(context, attrs);
    detector = new ScaleGestureDetector(getContext(), new ScaleListener());

    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();

    displayWidth = display.getWidth();
    displayHeight = display.getHeight();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
    switch (event.getAction() & MotionEvent.ACTION_MASK)
    {
    case MotionEvent.ACTION_DOWN:
    mode = DRAG;

    //We assign the current X and Y coordinate of the finger to startX and startY minus the previously translated
    //amount for each coordinates This works even when we are translating the first time because the initial
    //values for these two variables is zero.
    startX = event.getX() – previousTranslateX;
    startY = event.getY() – previousTranslateY;
    break;

    case MotionEvent.ACTION_MOVE:
    translateX = event.getX() – startX;
    translateY = event.getY() – startY;

    //We cannot use startX and startY directly because we have adjusted their values using the previous translation values.
    //This is why we need to add those values to startX and startY so that we can get the actual coordinates of the finger.
    double distance = Math.sqrt(Math.pow(event.getX() – (startX + previousTranslateX), 2) +
    Math.pow(event.getY() – (startY + previousTranslateY), 2)
    );

    if(distance > 0)
    {
    dragged = true;
    distance *= scaleFactor;
    }

    break;

    case MotionEvent.ACTION_POINTER_DOWN:
    mode = ZOOM;
    break;

    case MotionEvent.ACTION_UP:
    mode = NONE;
    dragged = false;

    //All fingers went up, so let’s save the value of translateX and translateY into previousTranslateX and
    //previousTranslate
    previousTranslateX = translateX;
    previousTranslateY = translateY;
    break;

    case MotionEvent.ACTION_POINTER_UP:
    mode = DRAG;

    //This is not strictly necessary; we save the value of translateX and translateY into previousTranslateX
    //and previousTranslateY when the second finger goes up
    previousTranslateX = translateX;
    previousTranslateY = translateY;
    break;
    }

    detector.onTouchEvent(event);

    //We redraw the canvas only in the following cases:
    //
    // o The mode is ZOOM
    // OR
    // o The mode is DRAG and the scale factor is not equal to 1 (meaning we have zoomed) and dragged is
    // set to true (meaning the finger has actually moved)
    if ((mode == DRAG && scaleFactor != 1f && dragged) || mode == ZOOM)
    {
    invalidate();
    }

    return true;
    }

    @Override
    public void onDraw(Canvas canvas) {

    canvas.save();

    //If translateX times -1 is lesser than zero, let’s set it to zero. This takes care of the left bound
    if((translateX * -1) (scaleFactor – 1) * displayWidth)
    {
    translateX = (1 – scaleFactor) * displayWidth;
    }

    if(translateY * -1 (scaleFactor – 1) * displayHeight)
    {
    translateY = (1 – scaleFactor) * displayHeight;
    }

    //We need to divide by the scale factor here, otherwise we end up with excessive panning based on our zoom level
    //because the translation amount also gets scaled according to how much we’ve zoomed into the canvas.
    canvas.translate(translateX / scaleFactor, translateY / scaleFactor);

    //We’re going to scale the X and Y coordinates by the same amount
    canvas.scale(scaleFactor, scaleFactor);

    super.onDraw(canvas);

    /* The rest of your canvas-drawing code */
    canvas.restore();
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
    {
    @Override
    public boolean onScale(ScaleGestureDetector detector)
    {
    scaleFactor *= detector.getScaleFactor();
    scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));
    return true;
    }
    }

    }

    ———————————

    And finally, if you want to call this class from an XML Layout file, below is an example. Note the prefix “IHM” before ImageZoomView, that is mandatory to inflate the layout.

  22. <IHM.ImageZoomView
    android:id=”@+id/imageViewPlan”
    android:layout_width=”match_parent”
    android:layout_height=”0dip”
    android:layout_weight=”0.61″
    android:src=”@drawable/ic_launcher” android:scaleType=”fitCenter” android:contentDescription=”@string/map_manager_click_position”/>

  23. Sorry, one more bug correction when translating and zoomed :

    package IHM;

    import android.content.Context;
    import android.graphics.Canvas;
    import android.util.AttributeSet;
    import android.view.Display;
    import android.view.MotionEvent;
    import android.view.ScaleGestureDetector;
    import android.view.WindowManager;
    import android.widget.ImageView;

    //http://vivin.net/2011/12/04/implementing-pinch-zoom-and-pandrag-in-an-android-view-on-the-canvas/8/

    public class ImageZoomView extends ImageView
    {
    //These two constants specify the minimum and maximum zoom
    private static float MIN_ZOOM = 1f;
    private static float MAX_ZOOM = 5f;

    private float scaleFactor = 1.f;
    private ScaleGestureDetector detector;

    //These constants specify the mode that we’re in
    private static int NONE = 0;
    private static int DRAG = 1;
    private static int ZOOM = 2;

    private int mode;

    //These two variables keep track of the X and Y coordinate of the finger when it first
    //touches the screen
    private float startX = 0f;
    private float startY = 0f;

    //These two variables keep track of the amount we need to translate the canvas along the X
    //and the Y coordinate
    private float translateX = 0f;
    private float translateY = 0f;

    //These two variables keep track of the amount we translated the X and Y coordinates, the last time we
    //panned.
    private float previousTranslateX = 0f;
    private float previousTranslateY = 0f;

    private boolean dragged = false;
    private float displayWidth;
    private float displayHeight;

    public ImageZoomView(Context context, AttributeSet attrs)
    {
    super(context, attrs);
    detector = new ScaleGestureDetector(getContext(), new ScaleListener());

    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();

    displayWidth = display.getWidth();
    displayHeight = display.getHeight();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
    switch (event.getAction() & MotionEvent.ACTION_MASK)
    {
    case MotionEvent.ACTION_DOWN:
    mode = DRAG;

    //We assign the current X and Y coordinate of the finger to startX and startY minus the previously translated
    //amount for each coordinates This works even when we are translating the first time because the initial
    //values for these two variables is zero.
    startX = event.getX() – previousTranslateX;
    startY = event.getY() – previousTranslateY;
    break;

    case MotionEvent.ACTION_MOVE:
    translateX = event.getX() – startX;
    translateY = event.getY() – startY;

    //We cannot use startX and startY directly because we have adjusted their values using the previous translation values.
    //This is why we need to add those values to startX and startY so that we can get the actual coordinates of the finger.
    double distance = Math.sqrt(Math.pow(event.getX() – (startX + previousTranslateX), 2) +
    Math.pow(event.getY() – (startY + previousTranslateY), 2)
    );

    if(distance > 0)
    {
    dragged = true;
    }

    break;

    case MotionEvent.ACTION_POINTER_DOWN:
    mode = ZOOM;
    break;

    case MotionEvent.ACTION_UP:
    mode = NONE;
    dragged = false;

    //All fingers went up, so let’s save the value of translateX and translateY into previousTranslateX and
    //previousTranslate
    previousTranslateX = translateX;
    previousTranslateY = translateY;
    break;

    case MotionEvent.ACTION_POINTER_UP:
    mode = DRAG;

    //This is not strictly necessary; we save the value of translateX and translateY into previousTranslateX
    //and previousTranslateY when the second finger goes up
    previousTranslateX = translateX;
    previousTranslateY = translateY;
    break;
    }

    detector.onTouchEvent(event);

    //We redraw the canvas only in the following cases:
    //
    // o The mode is ZOOM
    // OR
    // o The mode is DRAG and the scale factor is not equal to 1 (meaning we have zoomed) and dragged is
    // set to true (meaning the finger has actually moved)
    if ((mode == DRAG && scaleFactor != 1f && dragged) || mode == ZOOM)
    {
    invalidate();
    }

    return true;
    }

    @Override
    public void onDraw(Canvas canvas) {

    canvas.save();

    //If translateX times -1 is lesser than zero, let’s set it to zero. This takes care of the left bound
    if((translateX * -1) (scaleFactor – 1) * displayWidth)
    {
    translateX = (1 – scaleFactor) * displayWidth;
    }

    if(translateY * -1 (scaleFactor – 1) * displayHeight)
    {
    translateY = (1 – scaleFactor) * displayHeight;
    }

    //We need to divide by the scale factor here, otherwise we end up with excessive panning based on our zoom level
    //because the translation amount also gets scaled according to how much we’ve zoomed into the canvas.
    canvas.translate(translateX, translateY);

    //We’re going to scale the X and Y coordinates by the same amount
    canvas.scale(scaleFactor, scaleFactor);

    super.onDraw(canvas);

    /* The rest of your canvas-drawing code */
    canvas.restore();
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
    {
    @Override
    public boolean onScale(ScaleGestureDetector detector)
    {
    scaleFactor *= detector.getScaleFactor();
    scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));
    return true;
    }
    }
    }

  24. @WAIBE: Thank you for your fixes! As you can tell, I’m not that great with graphics and so a little bit of trial and error was involved. I’ll go ahead and post your updated code when I get home. Thanks once again!

  25. Humm… Not perfect yet, I still have issue : I try to solve it and post again later…

  26. Last bug correction : the display size must be the size of the view, not the size of the screen (view may be smaller than screen). So :

    displayWidth = this.getWidth();
    displayHeight = this.getHeight();

    (this = the view…)

  27. Hey hi,i m implementing android app,and i want zoom and swipe option at the same time.in view flipper..so you have any idea?

  28. Hello,

    I just wanted to thank you for the tutorial, it really helped me with this. I also managed to get the view to stay centered under your fingers when you zoom, and thought I’d share. Canvas.scale( … ) is overloaded. The second version of it takes in two points, for which you can pass in the getFocusX/Y points. For example, in onDraw() use

    canvas.scale(this.scaleFactor, this.scaleFactor, this.detector.getFocusX(), this.detector.getFocusY());

    instead of

    canvas.scale(this.scaleFactor, this.scaleFactor);

  29. @Chris Rogers: Thanks a bunch, Chris! That has been bothering me for a while! I’ll modify the code in my post as soon as I can.

  30. Can anyone please provide me a bug and error free code please.

  31. Fantastic work! I used the imageview class and then added Chris Rogers scaling.

    I still found a bug that makes the image jump a bit when transitioning between panning and zooming. You may have already found this but here’s what I did:

    In “case MotionEvent.ACTION_MOVE:” add the following and declare the variables at class level.

    if (mScaleDetector.isInProgress()){
    mLastGestureX = mScaleDetector.getFocusX();
    mLastGestureY = mScaleDetector.getFocusY();
    }

    next change the code that Chris mentioned slightly:

    if (mScaleDetector.isInProgress()){
    canvas.scale(mScaleFactor, mScaleFactor, mScaleDetector.getFocusX(), mScaleDetector.getFocusY());
    }
    else{
    canvas.scale(mScaleFactor, mScaleFactor, mLastGestureX, mLastGestureY);
    }

  32. how can we use this with an image view or with an image directly in android????
    please help

    thank you !!!

  33. Please i’m new to android could you provide a code for the activity? Thanks

  34. Hello,

    thanks for your work! Tried your solution and bugfixes in comments, but there are still small “bugs” which make the user experience not so great… ie dragging when zooming goes to the wrong direction, image jumps when zooming, image left and top bounds, image sometimes “lost” outside of screen… I mean it’s usable but not perfect.

    Anyway, found this on github, seems to work better for me, it’s worth giving it a try :
    https://github.com/MikeOrtiz/TouchImageView

  35. I am little bit newer to android. I want to apply pinch/zoom effects to a 3D object simply by fingers. I referred pinch/zoom effects for an image over ImageView. I tried to show the 3D objects (like tringle,cube) over GLSurfaceView. The objects can be shown, even can be rotated but I am failed to apply pinch/zoom effects to zoom the 3d objects.

    I am listing some of the links I’d gone through http://www3.ntu.edu.sg/home/ehchua/programming/android/Android_3D.html http://insanitydesign.com/wp/projects/nehe-android-ports/ http://x-tutorials.blogspot.sg/2011/11/implement-pinch-zoom-in-ontouchlistener.html

    I am providing my code here…

    public class MyGLActivity extends Activity {
    private GLSurfaceView glView; // Use GLSurfaceView
    public static Context context;

    // Call back when the activity is started, to initialize the view
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
    super.onCreate(savedInstanceState);

    glView = new MyGLSurfaceView(this); // Allocate a GLSurfaceView

    setContentView(glView); // This activity sets to GLSurfaceView
    }

    // Call back when the activity is going into the background
    @Override
    protected void onPause() {
    super.onPause();
    //glView.onPause();
    }

    // Call back after onPause()
    @Override
    protected void onResume() {
    super.onResume();
    // glView.onResume();
    }

    class MyGLSurfaceView extends GLSurfaceView
    {
    private final float TOUCH_SCALE_FACTOR = 180.0f / 320;
    private float mPreviousX;
    private float mPreviousY;

    // touch events
    private final int NONE = 0;
    private final int DRAG = 0;
    private final int ZOOM = 0;

    // pinch to zoom
    float oldDist = 100.0f;
    float newDist;

    int mode = 0;

    MyGLRenderer renderer;

    public MyGLSurfaceView(Context context)
    {
    super(context);

    renderer=new MyGLRenderer();
    setRenderer(renderer);

    // Render the view only when there is a change in the drawing data
    setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }

    public boolean onTouchEvent(MotionEvent event)
    {
    //Log.e(“tag”, “Touched”);
    //Toast.makeText(this.getContext(), “Touched”,Toast.LENGTH_SHORT).show();

    float x = event.getX();
    float y = event.getY();
    switch (event.getAction())
    {
    case MotionEvent.ACTION_DOWN: // one touch: drag
    mode = DRAG;
    break;
    case MotionEvent.ACTION_POINTER_DOWN: // two touches: zoom
    oldDist = spacing(event);
    if (oldDist > 10.0f) {
    mode = ZOOM; // zoom
    }
    break;
    case MotionEvent.ACTION_UP: // no mode
    mode = NONE;
    oldDist = 100.0f;
    break;
    case MotionEvent.ACTION_POINTER_UP: // no mode
    mode = NONE;
    oldDist = 100.0f;
    break;
    case MotionEvent.ACTION_MOVE: // rotation
    if (event.getPointerCount() > 1 && mode == ZOOM)
    {
    newDist = spacing(event);
    //Log.d(“SPACING: “, “OldDist: ” + oldDist + “, NewDist: ” + newDist);
    if (newDist > 10.0f)
    {
    float scale = newDist/oldDist; // scale
    // scale in the renderer

    Log.i(“Zoom”, “Zooming……”);

    oldDist = newDist;
    }
    }
    else if (mode == DRAG)
    {
    float dx = x – mPreviousX;
    float dy = y – mPreviousY;
    renderer.mAngleX += dx * TOUCH_SCALE_FACTOR;
    renderer.mAngleY += dy * TOUCH_SCALE_FACTOR;
    requestRender();
    }
    break;
    }
    mPreviousX = x;
    mPreviousY = y;
    return true;
    }
    }

    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);
    }
    }

    Could you please give me some demo example to do zoom effect to 3d cube?

  36. @Vishal Tavande: Hello Vishal, I think you may have better luck on StackOverflow. I haven’t worked with OpenGL stuff that much so I won’t be very much help. Hope you’re able to find a solution to your problem!

  37. can anyone upload an working project using this class?
    thx

  38. [...] pan in his view. Knowing that internet is your best friend he googled it and found the following blogpost. The post and code was well written and very easy to follow. But Ruffy wanted to do things [...]

  39. Hey thanks for a great post that was really easy to follow! I did my own implementation of the zoom view, but I based it mainly on your solution. If you’d like to see how to get the pinch to zoom in on where you are pinching, you can checkout the last chapter of my short blogpost: http://www.ruffythepirate.com/?p=128&preview=true#Zooming_to_where_you_pinch (it is basically about panning the view slightly as you are zooming in, based on the coordinates of where you are pinching.

  40. @Ruffy: Glad you found the post useful! I also took a look at your post and I like the solution that you used to keep the view centered!

  41. Hey,
    i’ve used this site
    http://android-developers.blogspot.de/2010/06/making-sense-of-multitouch.html
    to implement drag and zoom.
    Using the pivot points created an ugly behaviour on my hardware.
    That’s why I used the following formula to translate the canvas for centering:

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
    float old = mScaleFactor;
    mScaleFactor *= detector.getScaleFactor();

    // Don’t let the object get too small or too large.
    mScaleFactor = Math.max(0.5f, Math.min(mScaleFactor, 2.0f));

    float f = mScaleFactor / old – 1;
    float x = detector.getFocusX();
    float y = detector.getFocusY();

    mPosX -= f * (x – mPosX);
    mPosY -= f * (y – mPosY);

    invalidate();
    return true;
    }
    }

Leave a Reply

All original content on these pages is fingerprinted and certified by Digiprove