ActionScript.org Flash, Flex and ActionScript Resources - http://www.actionscript.org/resources
AS3 Photo Viewer Tutorial
http://www.actionscript.org/resources/articles/726/1/AS3-Photo-Viewer-Tutorial/Page1.html
Amen KAMALELDINE

Amen is an industrial informatics engineer. he studied the first four years of his education in Faculty of Engineering in Lebanon. He went to France to Continue his studies by doing the fifth year of engineering and the master TIS (Technologie Information et Système) in the same time at UTC (Université de Technologie de Compiègne). He also work as a freelancer in flash development field. He is always interessted in the new multimedia technologies.

 
By Amen KAMALELDINE
Published on February 21, 2008
 

Time: 40 minutes
Difficulty Level: Intermediate
Requirements: Flash CS3

in this tutorial you will learn how to create a smooth Photo Viewer, with an xml file to feed our application.
We will cover

  • the xml loading and parsing
  • Creating Dynamic Menu for our photo viewer
  • Resizing photo and extracting its bitmap data
  • creating reflection by  for a photo

Introduction

In this tutorial we will create an Action Script 3 photo viewer we will go through the xml in AS3 which will hold our photo links to generate our photo viewer data. Let’s take a look at the result we will be ended with.



Ok, let’s go through our xml file structure on the next page.


XML Structure
Our XML file will contain the URLs of our external photo's

Structure:

Our XML structure is actually pretty simple we have a root tag which named images and the sub items are named img and each one will contain the url to the images we need to load.
[code]
<images>
<img> url1 </img>
<img> url2 </img>
</images>
[/code]

To load an external file in AS3 we need to create a URLLoader object and a URLRequest object. Our xml file is named “data.xml” and the instance of the URLLoader object is called xmlLoader then all we have to do is to use the load method with the URLRequest object to load the xml file.

xmlLoader.load(new URLRequest("data.xml"));

What we need after this is to assign a load complete event which will be triggered after the loading of the xml has done.

xmlLoader.addEventListener(Event.COMPLETE,parseXML);

The parseXML is the method which will be executed asynchronously after the xml loading is done.

The call back function is in our public class that is assigned to our stage movie.


Private function parseXML(event){

//the event object contain our xml data in a string format, we will create our xml object from it

myXML:XML=new XML(event.target.data);

}

Event.target represent the URLLoader Object and the data proporety contain the loaded file data in a string fromat.

So after the loading let's do the parsing and the menu generation.


Parsing and Menu Generation
Here we create the menu dynamiclly based on the number of items loaded from the xml file and it will be centered to the top of our stage. let's examine the code of the parsing and the generation.
[as]
private function parseXML(event){
   //generate the xml object
   xml=new XML(event.target.data);
   //ignorre the white spaces
   xml.ignoreWhitespace=true;
   //generate the xmlList object
   xmlList=xml.img;
   //init the image pos
   imgPos=0;
   //and the total image number
   imgTotal=xmlList.length();
   //add the menu holder to the stage
   addChild(menuHolder);
   //create a menu item variable
   var menuItem:Sprite;
   //and a textfield
   var menuText:TextField;
   //assign a new format for the text field
   var format:TextFormat = new TextFormat();
   format.font = "_sans";
   format.color = 0xFFFFFF;
   format.align='center';
   format.size = 10;
   //loop on the items to generate the menu
   for(var i=0;i<xmlList.length();i++){
    //the text field
    menuText=new TextField();
    //size fo the text
    menuText.width=15;
    menuText.height=15;
    menuText.defaultTextFormat=format;
    //set the text
    menuText.text=i;//
    //not selectable
    menuText.selectable=false
    //show border
    menuText.border=true;
    menuText.borderColor=0xFFFFFF;
    //give it the name text
    menuText.name="text";
    
    menuItem=new Sprite();
    //add it to the menu item
    menuItem.addChildAt(menuText,0);
    //
    menuItem.x=i*(menuItem.width+10);
    //draw the back ground ot use the backgroundColor text field proprety instead
    menuItem.graphics.beginFill(0x333333,1);
    menuItem.graphics.drawRect(0,0,menuItem.width,menuItem.height);
    menuItem.graphics.endFill()
    //disable the mouse children
    menuItem.mouseChildren=false
    //enable the button mode and use the hand cursor
    menuItem.useHandCursor=true;
    menuItem.buttonMode=true;
    //add the on click event
    menuItem.addEventListener(MouseEvent.CLICK,handleItemClick);
    
    
    //add the menu item to the menu holder
    menuHolder.addChild(menuItem);
    
   }
   //reupdate the position of the childs
   resizeHandler(null);
   //start loading the images one after one   
   loadNextImage();
  }
[/as]

as you can see the code is commented. the points of the parsing is that in AS3 you can generate the list directly from accessing the XML.SUBITEMNAME. and this will generate for you an XMLList object which you can access each item as an XML Object and you can also retrieve the number of items using the length() method.

lets examine the ImageContainer Class in the next Page.

ImageContainer Class

This class that extends the Sprite Class will holds the bitmap data of an image loaded (we will see how the bitmap data is retrieved in the next page) and it will also create the reflection of the image dynamiclly.

Class Code
[as]//class to display a bitmap data with reflexion
class ImageContainer extends MovieClip {
 //bitmap object
 private var bmp:Bitmap;
 //reflection bitmap object
 private var reflexion:Bitmap;
 //ref bitmap data
 private var refData:BitmapData;
 //reflextion mask
 private var refmask:Sprite;
 public function ImageContainer() {
  //initialize the bmp and the reflexion object
  bmp=new Bitmap();
  reflexion=new Bitmap();
 
  //and add it to the image container
  this.addChild(bmp);
 }
 //setter for the bitmap data
 public function set imageData(value:BitmapData) {
  //if the old value is not null dispose the data
  if (bmp.bitmapData!=null) {
   bmp.bitmapData.dispose();
  }
  //set the new data
  bmp.bitmapData=value;
  //update the position
  reflexion.x=bmp.x=-bmp.width/2;
  bmp.y=-bmp.height/2;
  //flip the reflexion object
  reflexion.scaleY=-1;
  //force the smoothing
  bmp.smoothing=true;
  //create the reflextion bitmap data
  refData=new BitmapData(bmp.width,bmp.height/4);
  //and copy  the pixel for the bmp bitmapdata
  refData.copyPixels(bmp.bitmapData,new Rectangle(0,3*bmp.height/4,bmp.width,bmp.height/4),new Point(0,0));
  //set the bitmap objects bitmap data
  reflexion.bitmapData=refData;
  //set the smoothing to true
  reflexion.smoothing=true;
  //update the reflextion position
  reflexion.y=bmp.height/2+reflexion.height+5;
  //create the mask
  refmask=new Sprite();
  //fill it with a gradient color (see help files)
  var fillType:String = GradientType.LINEAR;
    var colors:Array = [0xFFFFFF, 0xFFFFFF];
    var alphas:Array = [0.5, 0];
    var ratios:Array = [0x00, 0xFF];
    var matr:Matrix = new Matrix();
    matr.createGradientBox(reflexion.width, reflexion.height, Math.PI/2, 0, 0);
    var spreadMethod:String = SpreadMethod.PAD;
    refmask.graphics.beginGradientFill(fillType, colors, alphas, ratios, matr, spreadMethod);
    refmask.graphics.drawRect(0,0,reflexion.width, reflexion.height);
   //see help files
    //update the ref mask postion
    refmask.x=reflexion.x;
    refmask.y=reflexion.y-reflexion.height;
    //cash as bitmap both the relexion mask and reflexion sprite
    refmask.cacheAsBitmap=true;
    reflexion.cacheAsBitmap=true;
    //set the mask on the reflextion
   reflexion.mask=refmask;
   //add the mask
  addChild(refmask);
  this.scaleX=1;
  this.scaleY=1;
  //add reflexion
  addChild(reflexion);
 }
}[/as]

In the next page we will examin the loading of the images one after another.


Images Loading

Ok so at this point we need to load the images resize it and retrieve the bitmapdata from it and then provide this data to a new ImageContainer Object. first of all we load the image in the loader Object, the loader object is added to the fakeHolder instance, after the loading of an image is done we use a resize function to resize the image with no distortion of its ratio, depending on the stage size, after the resizing we retrieve our bitmap date from the fakeHolder which is not resized by the way the loader instance is the one that is resized so the data u retrieve is for the resized image (less bitmapdata).

Loading Code

[as]//load the images via this function
  private function loadNextImage(){
   //Trivial Test to stop the loading
   if(imgPos==imgTotal){
    return;
   }
   //get the url
   var url:String=xmlList[imgPos].toString();
   //assign it to the urlRequest objecct
   urlRequest.url=url;
   //load it
   loader.load(urlRequest)
  }[/as]

the xmlList contain the list of our items each item have the url we retrieve it from it give it to the urlRequest and then loaded using the loader object.

OnImage Loaded Code

[as]private function onImageLoaded(event:Event) {
   trace('onImageLoaded');  
   //fix the size of the loaded which is inside the fakeHolder
    fixSize(loader);
    //create a new bitmap data
    myBitmapData= new BitmapData(loader.width, loader.height);
    //draw it from the fakeHolder to retrieve the data of the resized image
    myBitmapData.draw(fakeHolder);
    //creata a new image container
    var imgContainer:ImageContainer=new ImageContainer();
    imgContainer.imageData=myBitmapData;
    //set its position
    imgContainer.x=stageW*(imgPos)+imgPos*stageW/2;
   
    //add it to the image holder
    imgHolder.addChild(imgContainer);
    //and to the reference array
    item_array.push(imgContainer);
    //update the image position
    imgPos++;
    //triger the onAllImagesLoaded event
    if(imgPos>=imgTotal){
     onAllImagesLoaded();
     return;
    }else{
     //or load the next image
     loadNextImage();
    }
}[/as]

as u can see the fixSize function is called before retrieving the bitmap data.

Resizing Function
[as]private function fixSize(loader:Loader) {
   var sw:Number=1;
   var sh:Number=1;
   loader.scaleX=1;
   loader.scaleY=1;
   //depending on the stageW and stageH and without loosing the ratio
   //the loaded is resized
   if (loader.width>stageW) {
    sw=stageW/loader.width;
   }
   if (loader.height>stageH) {
    sh=stageH/loader.height;
   }
   var s:Number=Math.min(sw,sh);
   loader.width*=s;
   loader.height*=s;
  }[/as]
the first 2 vars are the ratios setted to 1, reinit the loader scale (because we use only one loader), compare the size of the loader with the stage size and the ratio is the minimum between them.

Now let's check the transitions function on the next page


Transition Function

Now we assign to the holder of all the images an ON_ENTERFRAME event and in it we perform some calculation to update the holder and the imgHolder postion and scale to have a nice transition between the images.

Code
[as]
private function dispatchOnEnterFrame(e){
   //if the diffrence=0 do nothing
   if(diffX==0)return;
   //else update the sliding of our image holder with easing
   imgHolder.x+=(finalX-imgHolder.x)/6;
   //and also update the current X aside but with a greater easing coef to have some retard in time
   //between the 2 transition
   cX+=(finalX-cX)/12;
   //generate a percentage
   var per=100*(1-(finalX-cX)/diffX);
   //and ease the current percentage depending on the current generated percentage
   //this will assure the smoothing and the continus animation
   percentage+=(per-percentage)/12;
   //update the scaling to have the zoom in out effect
   holder.scaleX=1-(.5)*Math.sin(Math.PI*percentage/100);
   holder.scaleY=1-(.5)*Math.sin(Math.PI*percentage/100);  
  }
[/as]

first of all if the difference between the selected image and the position of the imgHolder which is a child of the holder and contain the images IS  Zero then do nothing. else we perform the easing Out equation on the imgHolder we also do the same think on a fake variable called cX the current is but with a greater coef to have the same result but in a slower time after this we calculate the percentage in an easing Out equation too, so we never have a discontinuty in our transition and at the end we update the scale wich goes between 1 if the percentage is 0 or 100 and min .5 if the precentage is 50.

all the rest in the class of our movie is some basic stuff like the drawing of the background function and the ON_CLICK event of the menuItems. If you have any questions regarding this please go to the forums.


Thanks for reading, the files are attached below. Have fun ;)