By hunk143
26th May 2010.
14:49
first. lol.
nice tut. keep ‘em comin’.
0
0
Welcome to the second part of the Android tutorial on how to make your own zoom control like the one used in Sony Ericsson X10 Mini in the Camera and Album applications. Click here to read the first part of the tutorial.
Don’t forget to go to Android Market and download Sony Ericsson Tutorials, the app that collects all sample apps in this and other Sony Ericsson tutorials.
In this part of the tutorial we will build on the zoom application we started in part 1. As you might remember, in part 1 we finished with a zoom application that didn’t have any limits, we could zoom and pan into the void and back. In this tutorial we will introduce limits and we will also make sure that the pan always follows the finger as one would expect, as we in part 1 could see panning following the finger differently depending on the current zoom level. Below is a link to the source code for step 2 and the video showing what you will learn in the one finger zoom tutorial series.
[Download] One Finger Zoom sample project – Part 2 (218kb)
Remember this picture from part 1?

Images illustrating how the zoom state works, the dashed gray area represents what is shown in the view and the patterned area represents the content. On the left: Zoom is 1, pan-x and pan-y are both 0.5, in this state the image fits the screen perfectly. In the middle: Zoom is 2, pan-x and pan-y are still both 0.5, less content is now shown on the screen but will be scaled up. To the right: Zoom is 3, pan-x is 0.7 and pan-y is 0.833, we now see less of the image, only the top right corner, scaled up.
In the rightmost image we’re at the top right pan limit, this means the maximum pan values are 0.7 and 0.833 on the x- and y-axis respectively when the zoom level is 3. The actual limits are dependent on the level of zoom in that particular dimension, and the level of zoom in a particular dimension depends on the aspect quotient (quotient between content aspect ratio and view aspect ratio, refer to part 1 for more explanation). And to make a long story short, in order to apply limits to the pan, as well as making pan follow the finger correctly, we need the aspect quotient.
Currently though, from part 1, our aspect quotient is only available in the ImageZoomView. So first let us change this and create an object that holds the aspect quotient and extends Observable as a means of easily notifying whom ever wishes to observe.
[java]
public class AspectQuotient extends Observable {
private float mAspectQuotient;
public float get() {
return mAspectQuotient;
}
public void updateAspectQuotient(float viewWidth, float viewHeight, float contentWidth,
float contentHeight) {
final float aspectQuotient = (contentWidth / contentHeight) / (viewWidth / viewHeight);
if (aspectQuotient != mAspectQuotient) {
mAspectQuotient = aspectQuotient;
setChanged();
}
}
}
[/java]
Pretty straight forward, the calculations are the same as in part 1 and just as the ZoomState observable we leave it up to the client updating the aspect quotient to notify observers. The next step is to update ImageZoomView to hold a AspectQuotient object (as opposed to just a float as in part 1), and give others access to it. Of course we also need to update the code that currently modifies the aspect quotient, but I’ll leave code like this out of the tutorial as it’s straightforward and just re-factors existing functionality. Please see the code in the project linked below for reference.
Alright, so now we’re able to access the aspect quotient that we need for implementing limits and follow finger through a neat and tidy Observable. The next step is to add the actual code that does these things, but where?
Currently the responsibility for modifying the ZoomState lies with the OnTouchListener, while we could add the functionality here I am reluctant too as I want the OnTouchListener implementation to be responsible for the touch paradigm used and not the specifics on how the ZoomState is controlled. For example, I want to easily be able to implement an OnTouchListener for multi-touch, and when I do I don’t want to update or even duplicate the functionality that applies limits, or as we’ll see in later parts, animates a state.
Another idea would be to add the functionality to the ZoomState, but I want the ZoomState to be simple, it’s responsibility is to provide an interface for accessing the state and getting callbacks when it changes.
So, the way to implement the new functionality is through a new class that sits in between the OnTouchListener implementation and the ZoomState. Let’s call this new class BasicZoomControl, as we’ll implement a first and quite basic component for controlling the ZoomState. And let’s start with creating the skeleton of this class, we know we need access to the aspect quotient and we want a callback when this changes where we enforce limits, we also know we will be controlling a zoom state so lets add that and a get method for access.
[java]
public class BasicZoomControl implements Observer {
private AspectQuotient mAspectQuotient;
private final ZoomState mState = new ZoomState();
public ZoomState getZoomState() {
return mState;
}
public void setAspectQuotient(AspectQuotient aspectQuotient) {
if (mAspectQuotient != null) {
mAspectQuotient.deleteObserver(this);
}
mAspectQuotient = aspectQuotient;
mAspectQuotient.addObserver(this);
}
public void update(Observable observable, Object data) {
limitZoom();
limitPan();
}
}
[/java]
Secondly we want methods for applying zoom and pan and apply the logics needed to keep within limits, have pan follow finger and also we’ll be making the content under the coordinate of the touch down event invariant during zooming, meaning we can zoom into specific parts of the content and not just the current center.
[java]
public void zoom(float f, float x, float y) {
final float aspectQuotient = mAspectQuotient.get();
final float prevZoomX = mState.getZoomX(aspectQuotient);
final float prevZoomY = mState.getZoomY(aspectQuotient);
mState.setZoom(mState.getZoom() * f);
limitZoom();
final float newZoomX = mState.getZoomX(aspectQuotient);
final float newZoomY = mState.getZoomY(aspectQuotient);
mState.setPanX(mState.getPanX() + (x – .5f) * (1f / prevZoomX – 1f / newZoomX));
mState.setPanY(mState.getPanY() + (y – .5f) * (1f / prevZoomY – 1f / newZoomY));
limitPan();
mState.notifyObservers();
}
public void pan(float dx, float dy) {
final float aspectQuotient = mAspectQuotient.get();
mState.setPanX(mState.getPanX() + dx / mState.getZoomX(aspectQuotient));
mState.setPanY(mState.getPanY() + dy / mState.getZoomY(aspectQuotient));
limitPan();
mState.notifyObservers();
}
[/java]
The zoom method takes a zoom factor and the position of the touch down event as parameters and then updates the state of both the zoom level and the pan position. By storing the previous zoom levels it is possible to calculate new pan parameters that satisfies keeping the down coordinate invariant when zooming. The pan method is similar to how panning was done in part 1 but now the dx and dy values are divided by the level of zoom in the respective coordinate, doing this makes pan follow the finger as we now take the zoom level in consideration. Finally we need to implement the methods that limit the zoom and pan values:
[java]
private static final float MIN_ZOOM = 1;
private static final float MAX_ZOOM = 16;
private void limitZoom() {
if (mState.getZoom() < MIN_ZOOM) {
mState.setZoom(MIN_ZOOM);
} else if (mState.getZoom() > MAX_ZOOM) {
mState.setZoom(MAX_ZOOM);
}
}
private float getMaxPanDelta(float zoom) {
return Math.max(0f, .5f * ((zoom – 1) / zoom));
}
private void limitPan() {
final float aspectQuotient = mAspectQuotient.get();
final float zoomX = mState.getZoomX(aspectQuotient);
final float zoomY = mState.getZoomY(aspectQuotient);
final float panMinX = .5f – getMaxPanDelta(zoomX);
final float panMaxX = .5f + getMaxPanDelta(zoomX);
final float panMinY = .5f – getMaxPanDelta(zoomY);
final float panMaxY = .5f + getMaxPanDelta(zoomY);
if (mState.getPanX() < panMinX) {
mState.setPanX(panMinX);
}
if (mState.getPanX() > panMaxX) {
mState.setPanX(panMaxX);
}
if (mState.getPanY() < panMinY) {
mState.setPanY(panMinY);
}
if (mState.getPanY() > panMaxY) {
mState.setPanY(panMaxY);
}
}
[/java]
Limiting the zoom is quite straightforward, we use 1 (within screen bounds) and 16 (quite a lot of zoom, more than enough for most images on most screens) and simply clamp the value. For the pan we do the same but the problem with pan is that the limits change based on the level of zoom we’re currently at. For example, at a zoom level of 1 we don’t want the user to be able to pan at all (as the content fits the view perfectly).
Alright, we’re almost done with part 2 of the zoom tutorial. What’s left is adapting the Activity and the OnTouchListener implementations to the new solutions. This means changing so that the OnTouchListener implementation doesn’t manipulate the ZoomState directly but instead calls methods in BasicZoomControl, and setting it all up correctly in the Activity implementation. Please check out the project link below if you want to know the specifics of this code, or even better if you want to start playing around with the code on your own!
Now we have a zoom that works good, we’ve fixed the blemishes from part 1 but we’ve got some work left to make it really useful. First thing that comes in my mind is the input method, changing mode in the options menu is a poor choice. Because of this we’ll look into implementing a new OnTouchListener and a new input paradigm in the next tutorial.
[Download] One Finger Zoom sample project – Part 2 (218kb)
Good Luck!
By hunk143
26th May 2010.
14:49
first. lol.
nice tut. keep ‘em comin’.
0
0
By Nacho
27th May 2010.
10:32
Incredible!!!
Amazing effects.. please guys!! update our devices!!! I need this things!! hehe
0
0
By Kevin Gaudin
28th May 2010.
16:07
Please add the missing Greater Than signs in limitZoom and limitPan code
Thank you very much for these tutorials, that’s really useful for me !
0
0
By Andreas Agvard
31st May 2010.
14:52
@Kevin Gaudin
Big thanks, fixed now
0
0
By pskink
3rd June 2010.
14:09
you can see another pan/swipe/zoom control here:
http://www.anddev.org/resources/file/1988
though i’m not sure if you need to login to anddev.org first…
0
0
By Andreas Agvard
4th June 2010.
10:31
@pskink
Really like your fast zoom animation! Didn’t add the double click fast zoom feature to the tutorial, it will be left as an exercise for you readers ![]()
Part three will be out early next week. It will focus on improving how the zoom is controlled.
0
0
By pskink
5th June 2010.
09:54
@Andreas
glad you liked it
did you notice swipe/fling as well?
0
0
By abhishek
14th September 2010.
10:37
hi… i bought new sony ericson’s mini pro X10 on contract and surprisingly there is no zoom when you want to take a pic…i mean you can zoom in but once you clicked a pic…which is ridiculous….it has 5 mega pixel…i am so disappointed with you people…did not expect this from Sony…which is such a brand name in the market… i have one question can we integrate a camera option in it which has zoom in option when i want to take a pic….thnx
0
0
By Latina Kinahan
10th November 2010.
11:57
I love this piece. I really realized many things. I’ll ask pals to check it too.
0
0
By gosip news blog
27th January 2011.
17:26
Nice post.Thank you for taking the time to publish this information very useful!
0
0
Sort by