Building a Flickr Flash Display, Part 3
Resources: Kelvin Luck's flashr library, the MyTweenedPhotos.fla, MyTweenedPhotos Actionscript file and MyAutoTweenedPhotos.fla, MyAutoTweenedPhotos ActionScript for this tutorial.
In the first part of this series, we built a simple Flickr Service client,
using Kelvin Luck's flashr library, and in the second part we added behavior to
the display.
Using onRollOver and rollOut handlers, we let the user zoom any thumbnail
image to double its size. When zooming the thumbnails, we had to make sure, they
stay within the boundaries of the display. We accomplished this by moving the
thumbnails on the right and bottom edges one grid step to the left and/or
upwards before zooming. The word "zooming" may sound like a smooth movement from
a small size to a bigger one, but instead the image jumped from one size to the
other, and in some cases made a sudden change of position.
In this third part of the tutorial, we'll be smoothing this movement. We will also make a version with automatic zooming of randomly selected thumbnails, in effect mimicking the behavior of the original Flickr Badge.
The State of the Badge
In the original badge, the size of the thumbnail images are randomly zoomed one by one to double the size. The resizing animation is a tweening between a start value and an end value and may be done using the Tween class. To see how the versatile Tween class can be used in a simpler environment, you may want to take a look at "Using the Tween Class", where the size and transparency of a JPEG image is demonstrated.
As you will recall from the last experiment, the response listener
onPeopleGetPublicPhotos, takes care of the Person objects delivered by the
Flickr service.
It extracts the array of Photo objects, creates MovieClip's for holding the
images, loads the images and scales the MovieClips to fit within the badge.
An onPress handler is added to each MovieClip for taking the user to the web
page at Flickr for that particular Image.
Last time we added onRollOver and onRollOut handlers, so that a mouse roll over zoomed the image to double its size.
// add the onRollOver and onRollOut event handlers to zoom the image
mc.onRollOver = function(){
xTemp=this._x; // Save the original position
yTemp=this._y;
this._x = Math.min(xTemp, xMax);
this._y = Math.min(yTemp, yMax);
// Swap this mc to first free depth
this.swapDepths(freeDepth);
this._xscale=this._yscale=100;
}
mc.onRollOut = function(){
this._x = xTemp;
this._y = yTemp;
this._xscale=this._yscale=49;
}
In the onRollOver handler we save the original positions of the current , so
we can restore it later. We then move the clip horizontally or vertically if
needed.
We lift it on top of the other clips by swapping it to the first free layer and
scale it to its full size.
In the onRollOut handler we restore the position if it was changes and scales it down to its default size.
When the image thumbnails arrives, the function places each photo in a matrix of MovieClips on the Stage and resizes them to 49% of the original size. Finally it adds an onPress handler to each MovieClip. When the user clicks or presses on an image, a browser window is opened with the users Fickr page for that image.
Smoothing the Behavior
Using the Tween class and the accompanying easing functions or classes we can make the zooming much more elegant. As seen from the "Using the Tween Class" experiment, many different tweening types are at our disposal. any parameter of an object can be tweened between two different values, merely by instantiating the Tween class, i.e. calling its constructor.
someTween = new mx.transitions.Tween( object, property, function, begin, end, duration, useSeconds )
It takes the following arguments:
object The object to apply the tween to property The property to tween function The name of the easing class to use begin The start value of the property end The end value of the property duration The duration in seconds or the frame interval useSeconds A boolean to tell if we want the duration to be in seconds ( default=true) or frames
As before we create a new project, so we open the MyFlipPhotos.fla and change the include statement and call to the main() entry point.
// Using the Kelvin API wrapper, zooming onRollOver, onRollOut import com.petitpub.flickr.MyTweenedPhotos; MyTweenedPhotos.main();
We then save it as MyTweenedPhotos.fla in the project directory. In the MyFlipPhotos.as Actionscript file, we have to change the class name, the constructor and all other references to MyFlipPhotos to read MyTweenedPhotos and save it as MyTweenedPhotos.as.
To use the Tween class, we have to import the classes so we add the following import statements
import mx.transitions.Tween; import mx.transitions.easing.*;
Let's have a look at a first approach of the new onRollOver handler!
mc.onRollOver = function(){
// Swap this mc to first free depth
this.swapDepths(freeDepth);
var x = Math.min(this.xOrg, this.xMax);
var y = Math.min(this.yOrg, this.yMax);
// Tween the MovieClip
var mcXPos = new Tween(this, "_x", Regular.easeIn, this.xOrg, x, 0.5, true );
var mcYPos = new Tween(this, "_y", Regular.easeIn, this.yOrg, y, 0.5, true );
var mcXScale = new Tween(this, "_xscale", Regular.easeIn, 49, 100, 0.5, true);
var mcYScale = new Tween(this, "_yscale", Regular.easeIn, 49, 100, 0.5, true);
}
Not to make it more difficult, but a bit more consistent, I have changed the xTemp and yTemp variable names to xOrg and yOrg, because they are used as temporary holders of a MovieClip's original position during zooming.
As before, we swap the clip to the first free layer and position it at its
maximum allowed position.
The mcXPos and mcYPos Tweens will tween the clip position, if needed, from its
original to its final position in 0.5 seconds
The mcXScale and mcYScale Tweens at the same time will scale the clip to 100%,
making for a smooth zoom.
Using the names of the Tweens, we may later rewind the tweens, restoring the
image size and position.
There are problems with this solution though. For each mouse over, four new
Tweens will be created, resulting in a very unstable behavior.
The image will immediately go back to its original position and size an restart
the zooming.
To remedy this, we'll have to check if we are already zooming and in that case
not creating any Tweens
// Tween the MovieClip if not already zooming
if(!this.zooming){
this.zooming = true; // We are indeed zooming
var mcXPos = new Tween(this, "_x", Regular.easeIn, this.xOrg, x, 0.5, true );
var mcYPos = new Tween(this, "_y", Regular.easeIn, this.yOrg, y, 0.5, true );
var mcXScale = new Tween(this, "_xscale", Regular.easeIn, 49, 100, 0.5, true);
var mcYScale = new Tween(this, "_yscale", Regular.easeIn, 49, 100, 0.5, true);
}
In the for loop where the MovielClips are created, we add a Boolean variable zooming to each MovieClip and set it to false for the first tween to happen. Now the tweenings only take place if zooming = false for this clip, and as it starts the Boolean is set to true. Other clips can be zoomed concurrently though.
With that taken care, we could write an onRollOut handler, that calls rewind() on the Tween objects. That was also my first attempt, but I realized that posed some problems for the user. When an image is zoomed out, it naturally covers some other images, and when the user rolls out, some other image gets a roll on or mouse over. This makes it very hard to zoom out images in the middle of the badge, because every time you try to reach one of them, one of the outer images gets a mouse over first, and starts covering the image the user is after.
We could of course solve this problem by introducing a small delay before the
zooming action starts, which is normally done for tooltips.
I decided not to use the onRollOut handler at all, and instead set up an
interval, under which the zoomed out image is shown. That way, if the user
positions the mouse pointer over an image in the middle row, it will zoom as
soon as the overlapping image gets back to its normal position.
When a tween comes to its end an event handler onMotionFinished is called, if
we define one.
So we add this to the onRollOver handler.
mcYScale["onMotionFinished"] = function(){
intv = setInterval(restore, 1000, obj);
}
This handler sets an interval, at the end of which which the function
restore() is called. The arguments are the function to be called, the time
to wait and the object to work on. The intv variable point to the setInterval
object, or timer if you will, so that we later can interrupt the interval
execution of the restore function.
Note that as all Tweens takes the same amount of time, we may use any of them to
decide when tweening is finished.
Here is the restore function
// Restore all positions
function restore(mc){
clearInterval(intv);
mcXPos.rewind();
mcYPos.rewind();
mcXScale.rewind();
mcYScale.rewind();
mc.zooming=false; // We're ready to zoom again
}
It starts by interrupting the periodic calls to itself by calling
clearInterval() on this particular Interval.
Then it calls rewind on all Tweens and finally sets this MovieClip's state to
accept new requests for zooming.
Here is the full onRollOver handler we add to each MovieClip instance
mc.onRollOver = function(){
var intv;
// Swap this mc to first free depth
this.swapDepths(freeDepth);
var x = Math.min(this.xOrg, this.xMax);
var y = Math.min(this.yOrg, this.yMax);
// Tween the MovieClip if not already zooming
if(!this.zooming){
this.zooming=true; // We are indeed zooming
var obj=this;
var mcXPos = new Tween(this, "_x", Regular.easeIn, this.xOrg, x, 0.5, true );
var mcYPos = new Tween(this, "_y", Regular.easeIn, this.yOrg, y, 0.5, true );
var mcXScale = new Tween(this, "_xscale", Regular.easeIn, 49, 100, 0.5, true);
var mcYScale = new Tween(this, "_yscale", Regular.easeIn, 49, 100, 0.5, true);
mcYScale["onMotionFinished"] = function(){
intv = setInterval(restore, 1000, obj);
}
}
// Restore all positions
function restore(mc){
clearInterval(intv);
mcXPos.rewind();
mcYPos.rewind();
mcXScale.rewind();
mcYScale.rewind();
mc.zooming=false; // We're ready to zoom again
}
}
Random Automatic Zooming
The original Flickr Badge continuously zooms one randomly selected image a a time, without user intervention. The advantage of this behavior, is that it catches the eye of a visitor to your site. A slight drawback would be that the user cannot choose to take a closer look at a certain image, but has to wait for the display to select the image. In this last session of the tutorial, we'll make the badge fully automatic.
In the onRollOver version, the tweening of an image is triggered by a mouse over event. We simply have change that and trig on periodically fired events from some kind of timer. We have already used the functions setInterval and clearInterval in the onRollOver case, and we can use this to call a function to start a tweening zoom function.
As before, we create a new project by saving the old Flash and AS files as MyAutoTweenedPhotos.fla and MyAutoTweenedPhotos.as respectively, and changeing the class name and all references accordingly.
In the onPeopleGetPublicPhotos event handler, we set the interval for calling a select() function, that randomly selects one of the MovieClips holding our images.
var start = setInterval(select, 3000);
var sel:Number; // Currently selected image
var last:Number;// Lastly selected image
function select(){
// select an image too zoom
do{
sel = Math.round(Math.random() * photosArray.length);
}
while ( sel == last );
last = sel;
zoomIt(_root["photo" + sel]);
}
To avoid selection the same image in two consecutive instances, we keep track
of the last selection. the do{ } while statement takes care of that.
Math.random selects a float number between 0 and the photosArray.length and we
have to round it off to nearest integer to get a valid clip name.
When the MovieClips were created, they were given names like photo0, photo1,
photo2, etc. and attached to _root, so we can refer to them by _root["name"].
The select() function will be called every three seconds, selects a random image and calls the zoomIt function passing the selected image holder MovieClip.
function zoomIt(obj){
var intv;
// Tween the MovieClip if not already tweening
if(!zooming){
// Swap this mc to first free depth
obj.swapDepths(freeDepth);
zooming=true; // We are zooming
var x = Math.min(obj.xOrg, obj.xMax);
var y = Math.min(obj.yOrg, obj.yMax);
var mcXPos = new Tween(obj, "_x", Regular.easeIn, obj.xOrg, x, 0.5, true );
var mcYPos = new Tween(obj, "_y", Regular.easeIn, obj.yOrg, y, 0.5, true );
var mcXScale = new Tween(obj, "_xscale", Regular.easeIn, 49, 100, 0.5, true);
var mcYScale = new Tween(obj, "_yscale", Regular.easeIn, 49, 100, 0.5, true);
mcYScale["onMotionFinished"] = function(){
intv = setInterval(restore, 2000, obj);
}
}
// restore function goes here
}
The zoomIt() function works as the onRollOver() function in the interactive example above, and defines an onMotionFinished event handler, setting an interval after which the restore function is called.
// Restore all positions
function restore(mc){
clearInterval(intv);
mcYPos.rewind();
mcXPos.rewind();
mcXScale.rewind();
mcYScale.rewind();
// Ready to zoom again
zooming=false;
}// End restore
Note: The restore() function is internal to the zoomIt() function, which may not be regarded as good programming praxis. It is allowed in Actionscript though, and here it poses no problem, as zoomIt() is the only function using restore(). If we wanted to move restore() out to the same level as zoomIt(), we just have to declare the variables intv, mcXPos, mcYPos, mcXScale and mcYScale outside the zoomIt() function.
Now the user can lazily sit back and just whatch the Flickery show.
That's all for now. Experiment and enjoy!
Petit Labs