Implementing pinch-zoom and pan/drag in an Android view on the canvas
As far as what I’ve covered so far, here is the code its entirety; I hope you find it useful:
public class ZoomView extends View {
//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;
public ZoomView(Context context) {
super(context);
detector = new ScaleGestureDetector(getContext(), new ScaleListener());
}
@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) {
super.onDraw(canvas);
canvas.save();
//We're going to scale the X and Y coordinates by the same amount
canvas.scale(scaleFactor, scaleFactor);
//If translateX times -1 is lesser than zero, let's set it to zero. This takes care of the left bound
if((translateX * -1) < 0) {
translateX = 0;
}
//This is where we take care of the right bound. We compare translateX times -1 to (scaleFactor - 1) * displayWidth.
//If translateX is greater than that value, then we know that we've gone over the bound. So we set the value of
//translateX to (1 - scaleFactor) times the display width. Notice that the terms are interchanged; it's the same
//as doing -1 * (scaleFactor - 1) * displayWidth
else if((translateX * -1) > (scaleFactor - 1) * displayWidth) {
translateX = (1 - scaleFactor) * displayWidth;
}
if(translateY * -1 < 0) {
translateY = 0;
}
//We do the exact same thing for the bottom bound, except in this case we use the height of the display
else 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);
/* 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;
}
}
}
Popularity: 28% [?]
December 4, 2011 - Posted by vivin | Android, Java, Operating Systems, Programming and Development, Software | android, canvas, drag, dragging, gestures, java, pan, panning, pinch-zoom, view, zoom, zooming
17 Comments »
Leave a Comment Cancel reply
Search
Blogroll
Links
Popular Posts
Recent Tweets
- Beer http://t.co/ebFRWBAh 3 weeks ago
- @petermolydeux You are a dinosaur. Your task is to get fossilized, discovered by paleontologists, and then be displayed in the Smithsonian. 2012-03-06
- wtf wikipedia! #SOPA #PIPA 2012-01-18
- Twitter realtime search for "WTF wikipedia" is awesome! 2012-01-18
- More updates...
Powered by Twitter Tools
Recent Comments
- Bobby: @wauter: Could you please provide me active link to grinder-frameworked.py because i can`t find them in git.
- leo: great post! thank you very much
- Terence: Very interesting and useful information.
- Nj2toU: Thank you! I may have to do a factory reset of my Evo due to some kind of memory leak that shows me having...
- sbk: Thanks for this post which was most useful for me today. FYI org.apache.commons.beanutils.P ropertyUtils could...
Recent Trackbacks
- help paying rent: My opinion is ......
- Producing Emerging Media: Color Correcting and Photos in Wordpress
- Rough Book: Maven project for Generic (n-ary) Tree in Java
- Page 2 - Sprint Android Forum: Does Anyone Have a Shapewriter apk?
- JavaFX: LIS 7440
Random Posts
Tag Cloud
ait army army national guard asperger's syndrome asu baghdad camp liberty code cse421 dancing development dsl family fort lee freebsd garba hhb 1/180th inane india indian school al-ghubra iraq isg java Life linux message board my website national guard networking oif oman operation iraqi freedom perl photo album photos pictures programming project roughnecks salsa school vacation war weekend windows xpCategory Cloud
AIT Army Arts Assembly Computers Copyrights Dance Family and Friends FreeBSD Gaming Haiku Hardware Hosting Humor Java Jython Life Linux Love and Marriage Military Movies Music Musings, Ramblings, and Inanities Nerdy Stuff Networking Operating Systems Operation Iraqi Freedom p2p Perl PHP Poetry Politics and Law Programming and Development Projects Prose Python School Sci-Fi Science Software Television Travel Web Windows Work
Meta
Categories
- ►Arts (118)
- ►Art (2)
- ►Books (1)
- ►Concerts (1)
- ►Dance (15)
- ►Movies (6)
- ►Music (40)
- ►Bands (16)
- ►Coldplay (1)
- ►Metallica (3)
- ►Muse (1)
- ►Pink Floyd (1)
- ►Sigur Rös (1)
- ►The Beatles (1)
- ►The Shins (1)
- ►The Strokes (1)
- ►U2 (2)
- ►Coldplay (1)
- ►Genres (10)
- ►Indie (2)
- ►Rock (6)
- ►Alternative (2)
- ►Classic (1)
- ►Psychedelic (1)
- ►Alternative (2)
- ►Indie (2)
- ►Bands (16)
- ►Photography (3)
- ►Poetry (9)
- ►Haiku (4)
- ►Haiku (4)
- ►Prose (5)
- ►Television (5)
- ►Art (2)
- ►Economics (2)
- ►History (1)
- ►Life (537)
- ►Family and Friends (75)
- ►Humor (5)
- ►Love and Marriage (25)
- ►Musings, Ramblings, and Inanities (88)
- ►School (74)
- ►Travel (17)
- ►Work (23)
- ►Family and Friends (75)
- ►Military (198)
- ►Army (126)
- ►AIT (27)
- ►Annual Training (2)
- ►Basic Training (2)
- ►Operation Iraqi Freedom (24)
- ►AIT (27)
- ►Army (126)
- ►Nerdy Stuff (626)
- ►Computers (758)
- ►Hardware (22)
- ►Networking (41)
- ►Operating Systems (109)
- ►Programming and Development (301)
- ►Software (8)
- ►Hardware (22)
- ►DIY (1)
- ►Gaming (13)
- ►Linguistics (3)
- ►Math (1)
- ►Sci-Fi (10)
- ►Science (11)
- ►Technology (3)
- ►Technology (3)
- ►Computers (758)
- ►Politics and Law (39)
- ►Copyrights (10)
- ►DMCA (1)
- ►DMCA (1)
- ►Patents (1)
- ►Copyrights (10)
- ►Arts (118)
Archives
- 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
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();
}
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 !
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.
@Dan: @Roland:
Glad that you guys found the tutorial helpful!
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.
@BiggsyStudios:
Glad this was of help to you! Good luck with your coding!
Thank you for this! But where was displayWidth and displayHeight initialized?
In invalidate()a function that is already predefined within java/eclipse?
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
@Richard: Good catch! Thanks, I’ll update the post as soon as I can!
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!
Hi,
very informative tut i m using ur code but one prob is thr still i can drag image towards left top corner infinitely.
Hi,
ssorry to bug u guys again but how to keep the image in centre????????
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?
@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()?
great article.
I have a problem, how to zoom at finger click position? thanks
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