Home Labs Galleries PetitOn

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!