Skip to content

Rough Book

random musings

Menu
  • About Me
  • Contact
  • Projects
    • bAdkOde
    • CherryBlossom
    • FXCalendar
    • Sulekha
Menu

Implementing pinch-zoom and pan/drag in an Android view on the canvas

Posted on December 4, 2011July 17, 2020 by vivin

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.

Digiprove sealCopyright protected by Digiprove © 2020 Vivin PaliathSome Rights Reserved
Original content here is published under these license terms: X 
License Type:Attribution
License Abstract:You may copy this content, create derivative work from it, and re-publish it, provided you include an overt attribution to the author(s).
License URL:http://creativecommons.org/licenses/by/3.0/
Pages: 1 2 3 4 5 6 7 8

58 thoughts on “Implementing pinch-zoom and pan/drag in an Android view on the canvas”

Comments navigation

Newer comments
  1. Dan says:
    January 1, 2012 at 7:50 pm

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

    Reply
  2. Dan says:
    January 3, 2012 at 12:22 am

    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 !

    Reply
  3. Roland says:
    January 23, 2012 at 4:02 am

    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.

    Reply
  4. vivin says:
    January 23, 2012 at 9:42 am

    @Dan: @Roland:

    Glad that you guys found the tutorial helpful!

    Reply
  5. BiggsyStudios says:
    February 16, 2012 at 12:27 pm

    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.

    Reply
  6. vivin says:
    February 17, 2012 at 10:22 am

    @BiggsyStudios:

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

    Reply
  7. CP says:
    February 22, 2012 at 9:11 pm

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

    Reply
  8. Shannon says:
    February 29, 2012 at 4:43 pm

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

    Reply
    1. Sofiyan says:
      April 6, 2018 at 8:38 am

      Nope. It is an Android one 🙂

      Reply
  9. Richard says:
    March 11, 2012 at 8:08 am

    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 🙂

    Reply
  10. vivin says:
    March 12, 2012 at 2:14 pm

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

    Reply
  11. Giovanni says:
    March 20, 2012 at 4:02 am

    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!

    Reply
  12. cv says:
    March 23, 2012 at 4:23 am

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

    Reply
  13. cv says:
    March 23, 2012 at 4:32 am

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

    Reply
  14. so says:
    March 29, 2012 at 12:09 pm

    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?

    Reply
  15. vivin says:
    March 30, 2012 at 9:50 am

    @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()?

    Reply
  16. rain@morning says:
    April 19, 2012 at 7:19 pm

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

    Reply
  17. Latha says:
    April 21, 2012 at 6:38 am

    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

    Reply
  18. Joe says:
    May 25, 2012 at 1:08 pm

    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.

    Reply
  19. WAIBE says:
    June 20, 2012 at 1:28 pm

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

    Reply
  20. WAIBE says:
    June 20, 2012 at 2:17 pm

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

    Reply
  21. WAIBE says:
    June 20, 2012 at 2:28 pm

    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.

    Reply
  22. WAIBE says:
    June 20, 2012 at 2:30 pm

    <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”/>

    Reply
  23. WAIBE says:
    June 20, 2012 at 2:49 pm

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

    Reply
  24. vivin says:
    June 20, 2012 at 3:13 pm

    @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!

    Reply
  25. WAIBE says:
    June 20, 2012 at 3:34 pm

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

    Reply
  26. WAIBE says:
    June 23, 2012 at 3:49 am

    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…)

    Reply
  27. prashant says:
    July 4, 2012 at 12:32 am

    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?

    Reply
  28. Chris Rogers says:
    July 12, 2012 at 11:23 am

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

    Reply
  29. vivin says:
    July 14, 2012 at 10:08 am

    @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.

    Reply
  30. Kashyap says:
    July 17, 2012 at 7:57 am

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

    Reply
  31. Hank Finley says:
    August 29, 2012 at 12:01 pm

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

    Reply
  32. rahul meena says:
    September 17, 2012 at 2:13 am

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

    thank you !!!

    Reply
  33. Pingback: View with horizontal and vertical pan/drag and pinch-zoom | Jisku.com - Developers Network
  34. Pingback: View with horizontal and vertical pan/drag and pinch-zoom | Free Android Enthusiasts
  35. andrea says:
    October 4, 2012 at 8:50 am

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

    Reply
  36. Gapanyc says:
    October 19, 2012 at 2:41 am

    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

    Reply
  37. Vishal Tavande says:
    January 28, 2013 at 1:29 am

    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?

    Reply
  38. vivin says:
    January 30, 2013 at 10:49 am

    @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!

    Reply
  39. Tavi says:
    February 1, 2013 at 7:32 am

    can anyone upload an working project using this class?
    thx

    Reply
  40. Pingback: Zoom and Pan in Android | Ruffy the pirate
  41. Ruffy says:
    February 9, 2013 at 4:09 pm

    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.

    Reply
  42. vivin says:
    February 10, 2013 at 10:57 am

    @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!

    Reply
  43. Lindemann says:
    April 18, 2014 at 7:45 am

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

    Reply
  44. otmane says:
    May 3, 2014 at 5:01 pm

    it’s a great tutorial , but i’ve got a small problem !
    normally i draw small circles with a message inside associated to the MotionEvent.Action_Up , so when i clique on them i show the message inside ! but the problem is that after i zoom i don’t know how to test on their coordinate ! like if their place change according to their first coordinate saved on a vector ! i need o formula to associate their original coordinate to the new coordinate after zooming !
    CAN ANYONE HELPS ME PLEASE !
    that’s my e-mail : [email protected]
    THANKS a LOT

    Reply
  45. Iram says:
    May 18, 2014 at 11:17 pm

    Hie All,
    Please help me out.I want to draw on scaled canvas with accuracy.Is there any way to do that?If yes how? I am totally stucked please do help me out as sharing knowledge always succeeds.Thanks alottttt.

    Reply
  46. Quakig says:
    June 20, 2014 at 12:57 pm

    Hey Vivian

    Thanks for this amazing tutorial.. works good for me except for a small strange issue I am struggling to correct.

    When I drag to pan , the pan works in opposite direction. So if i drag left, the screen drags right.
    While nobody else complained of this strange behavior , I am struggling to get it right.

    I tries to change the sign of parameters in the line:
    canvas.translate(dx, dy) in your code but it doesn’t work.

    Any suggestions on where it could be wrong ?

    Reply
  47. Ammad Amjad says:
    March 11, 2015 at 1:08 am

    hi everyone ,i tried to follow this tutorial , but it doesn’t works for me . below is my code . can anyone please check and let me knew please , ill be really greatful . thanks in advance .

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

    if (type == Constants.DRAW_CIRCLE) {
    canvas.save();
    canvas.scale(scaleFactor, scaleFactor);
    canvas.translate(100 / scaleFactor, 100 / scaleFactor);

    for (CanvasDrawable model : drawingList) {
    model.draw(canvas);
    }
    canvas.restore();
    return;
    }

    for (CanvasDrawable model : drawingList) {
    model.draw(canvas);
    }

    }

    Reply
  48. Hans Milling says:
    August 31, 2015 at 12:56 pm

    How do I put the view inside my app? I followd the Android Studio tutorial on the android developer page. I have an activity with a linear layout, but do I need to scratch this and have the ZoomView instead, or does the ZoomView go inside the linear layout?

    Best regards
    Hans Milling…

    Reply
    1. vivin says:
      September 3, 2015 at 10:22 am

      ZoomView is a class I created. In your case I think all you would have to do is implement these methods inside your view. Keep in mind though this approach is rather dated, and so there are probably better ways of doing this today!

      Reply
  49. Amir says:
    September 20, 2015 at 7:52 am

    I’m trying to get this functionality to work with GridView. I made ZoomView extend GridView, but it seems nothing happening. Any ideas?

    Reply
    1. Lucas Senechal says:
      March 7, 2016 at 5:32 pm

      Hey Amir! i am struggling with the same thing! Were you able to figure this out? If so i would like to know how you did it!

      Reply
  50. Prokash Sarkar says:
    September 3, 2016 at 10:20 am

    Hello vivin, Thanks for posting your solution for such scenario. This post really helped me a lot. I got 2 questions,

    According to WAIBE’s fix,

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

    What would be the correct condition?

    (translateX * -1) == (scaleFactor – 1) * displayWidth

    or

    (translateX * -1) < (scaleFactor – 1) * displayWidth

    Secondly,

    While zooming at the max level, the drawing path isn't properly scaled & somehow shifted to a non accurate touch point.

    Reply

Comments navigation

Newer comments

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Meta

  • Log in
  • Entries feed
  • Comments feed
  • WordPress.org

Archives

  • February 2023
  • April 2020
  • February 2020
  • January 2020
  • December 2019
  • November 2019
  • September 2019
  • August 2019
  • July 2019
  • June 2019
  • May 2019
  • March 2019
  • February 2019
  • January 2019
  • December 2018
  • November 2018
  • September 2018
  • August 2018
  • July 2018
  • June 2018
  • May 2018
  • April 2018
  • March 2018
  • February 2018
  • January 2018
  • December 2017
  • November 2017
  • October 2017
  • June 2017
  • March 2017
  • November 2016
  • August 2016
  • July 2016
  • June 2016
  • February 2016
  • August 2015
  • July 2014
  • June 2014
  • March 2014
  • December 2013
  • November 2013
  • September 2013
  • July 2013
  • June 2013
  • March 2013
  • February 2013
  • January 2013
  • October 2012
  • July 2012
  • June 2012
  • January 2012
  • December 2011
  • November 2011
  • October 2011
  • September 2011
  • July 2011
  • June 2011
  • May 2011
  • February 2011
  • January 2011
  • December 2010
  • November 2010
  • October 2010
  • September 2010
  • July 2010
  • June 2010
  • May 2010
  • April 2010
  • March 2010
  • January 2010
  • December 2009
  • November 2009
  • October 2009
  • September 2009
  • August 2009
  • July 2009
  • May 2009
  • April 2009
  • March 2009
  • February 2009
  • January 2009
  • December 2008
  • November 2008
  • October 2008
  • August 2008
  • March 2008
  • February 2008
  • November 2007
  • July 2007
  • June 2007
  • May 2007
  • March 2007
  • December 2006
  • October 2006
  • September 2006
  • August 2006
  • June 2006
  • April 2006
  • March 2006
  • January 2006
  • December 2005
  • November 2005
  • October 2005
  • September 2005
  • August 2005
  • July 2005
  • June 2005
  • May 2005
  • April 2005
  • February 2005
  • October 2004
  • September 2004
  • August 2004
  • July 2004
  • June 2004
  • May 2004
  • April 2004
  • March 2004
  • February 2004
  • January 2004
  • December 2003
  • November 2003
  • October 2003
  • September 2003
  • July 2003
  • June 2003
  • May 2003
  • March 2003
  • February 2003
  • January 2003
  • December 2002
  • November 2002
  • October 2002
  • September 2002
  • August 2002
  • July 2002
  • June 2002
  • May 2002
  • April 2002
  • February 2002
  • September 2001
  • August 2001
  • April 2001
  • March 2001
  • February 2001
  • January 2001
  • December 2000
  • November 2000
  • October 2000
  • August 2000
  • July 2000
  • June 2000
  • May 2000
  • March 2000
  • January 2000
  • December 1999
  • November 1999
  • October 1999
  • September 1999
©2023 Rough Book | Built using WordPress and Responsive Blogily theme by Superb
All original content on these pages is fingerprinted and certified by Digiprove