Anda di halaman 1dari 16

AS3 Tutorial: Creating a Slideshow

by Armand Niculescu

AS3 Tutorial Creating a Slideshow


by Armand Niculescu
armand@media-division.com

Some of my old friends keep asking me for tips and tutorials for flash and actionscript, so I decided to spend a few hours writing a small tutorial for a small slideshow class. The tutorial turned out to be rather long, because I wanted to go in detail and explain the "why", not just the "how". Below is a screenshot of a site using this very Slideshow:

The code is AS3 and the tutorial assumes some general knowledge of flash and actionscript 1 and 2. We'll cover a number of topics in the following pages, including: classes and packages, AS3-style events, working with XML, loading data, animation, dynamic fonts and more. The table of Contents is as follows: 1. 2. 3. 4. 5. 6. 7. 8. 9. Class usage Loading the XML Parsing the XML Loading the images Adding to Stage Animating Finishing Touches Complete Code Improving and Extending

Class usage
We're aiming for simplicity here, so reducing the code to minimum, to create a new slideshow, you'd write:
var slideshow:Slideshow = new Slideshow("list.xml"); addChild(slideshow);

The first line should create an instance of the Slideshow class, while the second one would simply add it to the Stage (no more attachMovie!). Therefore, the barebone structure of our class will look like this:
2008 MediaDivision.com 1

AS3 Tutorial: Creating a Slideshow


package {

by Armand Niculescu

public class Slideshow extends Sprite { import flash.display.*; public function Slideshow(datasource:String) { } }

Doesn't really do much for now, does it? As you can see, in AS3 we need to specify the package for the class. If you've worked with packages in AS2, instead of writing something like
class com.widgets.Slideshow { ...

you now write


package com.widgets { class Slideshow { ...

In this tutorial we won't be using fancy packages, because the Slideshow is not part of a bigger framework although you're free to integrate it in one. Note how we're importing the flash.display package so we can extend the Sprite class. By the way, Sprite is a new kind of MovieClip without timeline (technically MovieClip extends Sprite by adding timeline functionality) but the point is that if you don't need your clip to have multiple frames, you're better off with a Sprite as it uses less RAM.

Loading the XML


The XML file structure is very simple:
<slideshow> <item src="filename1.jpg" title="title1"/> <item src="filename2.jpg" title="title2"/> ... </slideshow>

To load it, lets change the class a bit to load the XML:
package { import flash.display.*; import flash.events.*; import flash.net.*; public class Slideshow extends Sprite { private var xmlLoader:URLLoader; private var datasource:String; public function Slideshow(datasource:String) { this.datasource = datasource; xmlLoader = new URLLoader(new URLRequest(datasource)); xmlLoader.addEventListener(Event.COMPLETE, onDataLoaded);

private function onDataLoaded(e:Event):void

2008 MediaDivision.com

AS3 Tutorial: Creating a Slideshow


{ if (!xmlLoader.data) { trace("XML Load failed"); return; } } } } trace("XML loaded");

by Armand Niculescu

If you were used to the simple getURL method in AS2, things are slightly more complicated now, but not much. The Loader and URLLoader classes now replace all the different loadMovie, getURL, loadVariables, sendAndLoad methods and the MovieClipLoader class, so it's a good thing. The event model has also been changed from the ground up. A thorough explanation of how events work in AS3 exceeds the scope of this tutorial; suffice to say that xmlLoader.addEventListener(Event.COMPLETE, onDataLoaded) adds the onDataLoaded function as a listener of the xmlLoader "complete" event.

Parsing the XML


In AS2 I used to have an XML-to-Object recursive function that served me well over the years, since I found navigating the XML DOM rather unpleasant. AS3 has an XPath-style navigation, so parsing the XML is simply fun. However, before parsing the actual data, lets consider the functionality a bit. What we want is for the images to load in background and for the slideshow to start working when the first image has finished loading and then wait if needed for the next image to become available; for this, we'll need a FIFO (first in, first out) queue.
public class Slideshow extends Sprite { private var queue:Array = []; ...

the XML parsing will then be done like this:


private function onDataLoaded(e:Event):void { if (!xmlLoader.data) { trace("XML Load failed"); return; } var dataXml:XML = XML(xmlLoader.data); for each (var item:XML in dataXml.item) queue.push({url:item.@src, title:item.@title});

In theory, we don't even need the queue array, but it demonstrates how easily you can iterate trough nodes and read attributes. One potential source of confusion is the var dataXml:XML = XML(xmlLoader.data) part. xmlLoader is an instance of the URLLoader class, which has a .data property that simply keeps whatever was loaded from the server. The data property is untyped, so we must cast is as XML (constructs like xmlVar = XML(someVar) or num = Number(var) are called casts, a way of saying "treat this data as a Number").
dataXml.item returns all item nodes in DataXml. With for each we then go through each of these nodes; For every item node, we get its attributes using @src and @title and place them in the queue.

2008 MediaDivision.com

AS3 Tutorial: Creating a Slideshow

by Armand Niculescu

Loading the images


Now that we have the queue, we can start loading the images, one by one. The idea is to start loading an image only after the previous one has finished, so we can position it and generally keep things under control.
private function loadNextItem():void { // get the first item in queue var item:Object = queue.shift(); // stop when the queue is empty if (item==null) return; // initialize the loader for each load request loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.INIT, onItemLoaded); loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onItemError); loader.load(new URLRequest(item.url)); }

The code should be pretty straightforward, especially if you read the comments. As you can see, we're loading the image in a very similar fashion to the XML. As I wrote above, different loading operations are handled now in an unified way. Note how we're defining two listeners, one for the "init" event (when the resource is loaded and initialized) and one for the "io_error" event, when we know that the image couldn't be loaded. Don't forget that you need to define the loader variable at the top of your class definition.
private var loader:Loader;

Now lets handle the load event. First, we need to take into consideration the fact that maybe not all the images will actually load some of them may be missing and fail or be in an unreadable format. Therefore we need a counter to tell us how many images have actually loaded and also we need to copy the titles for the correctly loaded pictures into a separate array (the queue array is emptied by as images get loaded), so we know which title goes with the first image, which with the second one and so on. Second, because we want a nice wrap-around effect for the slideshow, we'll have to duplicate the first and last images so when the animations jumps from the end at the beginning, the user doesn't notice. It's a little hard to explain if you haven't done this before, but imagine that you have five images
1 2 3 4 5

to get the wrap-around effect, you need to duplicate the first and last images, like this:
5 1 2 3 4 5 1

Now the beginning matches the end (the "5 1" sequence) and it will look like an infinite band. Now, because we have to add some bits of code in several areas, I'm going to list the whole code so far (as usual, new stuff in bold):
package { import import import import flash.display.*; flash.events.*; flash.net.*; flash.text.*;

public class Slideshow extends Sprite { private const PIC_SPACING:int = 2; private var queue:Array = [];

2008 MediaDivision.com

AS3 Tutorial: Creating a Slideshow


private var titles:Array = []; private var loader:Loader; private var xmlLoader:URLLoader; private var datasource:String; private var xPos:int; private var loadedImages:int; private var crtTitle:String; private var container:Sprite; private var titleField:TextField; public function Slideshow(datasource:String) { container = new Sprite(); addChild(container); this.datasource = datasource; xmlLoader = new URLLoader(new URLRequest(datasource)); xmlLoader.addEventListener(Event.COMPLETE, onDataLoaded);

by Armand Niculescu

private function onDataLoaded(e:Event):void { if (!xmlLoader.data) { trace("XML Load failed"); return; } var dataXml:XML = XML(xmlLoader.data); for each (var item:XML in dataXml.item) queue.push({url:item.@src, title:item.@title}); } loadNextItem();

private function loadNextItem():void { // get the first item in queue var item:Object = queue.shift(); // stop when the queue is empty if (item==null) return; // keep this until the image has loaded, so we can assign crtTitle = item.title; // initialize the loader for each load request loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.INIT, onItemLoaded); loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onItemError); loader.load(new URLRequest(item.url)); } private function onItemLoaded(e:Event):void { loadedItems++; var content:Bitmap = Bitmap(loader.content); content.x = xPos; xPos += content.width + PIC_SPACING; container.addChild(content); titles.push(crtTitle); if (queue.length>0) loadNextItem(); else { //duplicate first and last items for wrap-around-effect var refFirst:Bitmap = Bitmap(container.getChildAt(0)); var refLast :Bitmap = Bitmap(container.getChildAt(container.numChildren-1));

2008 MediaDivision.com

AS3 Tutorial: Creating a Slideshow


// sadly, duplicateMovieClip is gone so we use this; // thank God it's not a MovieClip we needed to duplicate var copyFirst:Bitmap = new Bitmap(refFirst.bitmapData); var copyLast:Bitmap = new Bitmap(refLast.bitmapData); copyLast.x = -copyLast.width - PIC_SPACING; copyFirst.x = xPos + PIC_SPACING; container.addChild(copyFirst); container.addChild(copyLast);

by Armand Niculescu

} }

private function onItemError(e:Event):void { trace("load error"); loadNextItem(); } } }

Quite some code already, isn't it? Let's see what I've added. First you'll notice the private const PIC_SPACING definition for the distance between two images. Sure, we could define variables in AS2 and pretend they're constants, but now the constant behavior is actually enforced. Second, you'll notice the int data type for xPos and loadedImages, which is generally a better way to work with numeric variables that you know will only be integer. As a small bonus, variables of int type are automatically initialized with 0. I've also created a container Sprite that will keep all the images we load. The heavy part is the onItemLoaded function. See how loading an image is really not really that different from loading an XML? We're using a more generic Loader class instead of the URLLoader, but the principle is the same - we cast the result as a Bitmap and then place it inside our container. For items that have loaded successfully, we also keep the title in an array. Things are just a little trickier when it comes to duplicating the first and the last images. In AS3 we don't have a duplicateMovieClip, so duplicating is more cumbersome and involves three stages: get a reference of the bitmap we want to duplicate (getChildAt()); copy its contents (.bitmapData) ; placing the contents in a new bitmap (new Bitmap(content)). In the case the image couldn't load, we just move to the next item, ignoring the error.

Adding to Stage
If now we want to create the textfield that will display the title of the image currently in center of the screen, you might want to add something like this in constructor:
public function Slideshow(datasource:String) { titleField = new TextField(); titleField.x = 0; titleField.width = stage.stageWidth; titleField.y = stage.stageHeight - 30; ...

Note that you'll also need to import flash.text package like this: import flash.text.* If you run the code now, you will get a runtime error:
TypeError: Error #1009: Cannot access a property or method of a null object reference.

Why?

2008 MediaDivision.com

AS3 Tutorial: Creating a Slideshow

by Armand Niculescu

Each display object (MovieClip, Sprite, etc.) has a reference to the Stage, so instead of writing Stage.width (AS2), we use this.stage.stageWidth. There is a catch though: the reference to stage is created only when the object is added to stage (makes sense). When we create the class, we write
var slideshow:Slideshow = new Slideshow("list.xml"); addChild(slideshow);

so the constructor executes in the first line, and only after that the class is added on Stage via
addChild.

What do we do? There are many workarounds, but probably the best one is to take advantage of the new "added_to_stage" event that is fired just after the object has been displayed. Everything that requires the stage will be moved there:
private function onAddedToStage(e:Event):void { // disable stage scaling stage.scaleMode = StageScaleMode.NO_SCALE; // move container off-screen container.x = stage.stageWidth; // create the title field titleField = new TextField(); titleField.x = 0; titleField.width = stage.stageWidth; titleField.y = stage.stageHeight - 30; titleField.height = 30; titleField.selectable = false; // text format for the title var format:TextFormat = new TextFormat(); format.font = "Verdana"; format.align = TextFormatAlign.CENTER; format.size = 20; titleField.defaultTextFormat = format; titleField.text = "TEST"; addChild(titleField); }

The code should be pretty much self-explanatory. Obviously, in order for it to execute, you'll have to add a listener for it in the constructor:
public function Slideshow(datasource:String) { addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); ...

Animating the images


Believe it or not, we're almost there. The only major piece of functionality left to add is the actual animation of the images, and we already have almost everything in place. Add the code for the function:
private function animate(e:TimerEvent):void { }

In order for the animation to trigger, we'll use the new Timer class, which replaces setInterval (and actually adds more control), so add the following in the onAddedToStage() function:
private function onAddedToStage(e:Event):void { ... [old code]

2008 MediaDivision.com

AS3 Tutorial: Creating a Slideshow


... animator = new Timer(ANIMATION_INTERVAL); animator.addEventListener(TimerEvent.TIMER, animate); animator.start();

by Armand Niculescu

and define
private const ANIMATION_INTERVAL:int = 50;

This way, the animate function will be called every 50 milliseconds. Now, the animation will first have to follow some rules: - one image will be shown in the middle for X seconds; then the next item should slide in, but only if it has loaded; the animation speed should have some sort of "ease out" / braking effect. Add this to the animate() function:
// don't do anything if there isn't at least one image if (loadedItems<1) return; // check if there is a "next" image to display if (container.numChildren > displayedItems) { var picToShow:Bitmap = Bitmap(container.getChildAt(displayedItems)); var targetX:int = stage.stageWidth/2; var itemX:int = picToShow.x + picToShow.width/2 + container.x; var delta:int = (itemX-targetX) / ANIMATION_SPEED_FACTOR; } else { // the next image hasn't loaded yet, keep showing this one var picToShow:Bitmap = Bitmap(container.getChildAt(displayedItems-1)); var delta:int = 0; }

Don't forget to define private var displayedItems:int; in your class. The difference between loadedItems and container.numChildren is a little subtle. While the images are still loading, the two variables are equal; after the last image has loaded, the first and last image are duplicated, so the numChildren becomes loadedItems+2. We use
container.numChildren to

determine how many images are available. The idea is that if 5 images have loaded, and currently in the middle is image 3, we can animate the images container to reveal the next one. For this, I'm using a very simplistic method: We calculate the difference between the center of the Stage (stage.stageWidth/2) and the center of the next image to show in full (picToShow.x + picToShow.width/2 + container.x) on X axis (width). With each iteration, the container is supposed to travel a fraction of that distance. Suppose the initial difference is 100 pixels; if the cut factor is 2, at the first iteration the container will be moved to the left 50 pixels (100/2), then 25, 13, 7, 4, 2, 1, 0 (delta is an integer, so it takes no fractional values). This effectively gradually slows down the animation. For practical purposes I've found
2008 MediaDivision.com 8

AS3 Tutorial: Creating a Slideshow


private const ANIMATION_SPEED_FACTOR:int = 6;

by Armand Niculescu

to work best in my case. This whole approach works well only if the Slideshow displays 2-3 images at any time (one fully visible in the middle and two partially visible on its left and right). If the images are not wide enough, some white space will be visible on the right side when the animation reaches the end. If you intend to make a gallery that displays any number of images at the same time, you'll have to get rid of the whole "image in center" concept and make the delta calculations based on the left/right edge. Next, let's handle the (easy) situation when delta is 0, meaning that the image is stopped. We have two possibilities there - either the image has just stopped after an animation, in which case we should display the slide title, or that the current image has been displayed long enough and it's time to show the next one. For checking the time elapsed, we could use the Timer class, but I prefer an old friend: getTimer(). To use it, you must first import the utils package.
import flash.utils.*;

Note: if you're unfamiliar with getTimer(), it returns the number of milliseconds since the flash movie has started playing. The difference between two getTimer() values at different points in time gives us the time elapsed in between calls. Because of that, we must define another variable to store the previous reading:
private var lastTime:int;

I'll also declare


private const ANIMATION_STILL_TIME:int = 4;

as the number of seconds an image should stay still before the next one slides in. This code gets added to animate():
if (delta<=0) { // the next image should be displayed... if (((getTimer()-lastTime) >= ANIMATION_STILL_TIME * 1000) && (loadedItems > displayedItems)) { lastTime = getTimer(); displayedItems++; titleField.text = ""; } else { // we should display the title for the current image if (titleField.text == "" && titles[container.getChildIndex(picToShow)] != null) titleField.text = titles[container.getChildIndex(picToShow)]; } }

Piece of cake!

2008 MediaDivision.com

AS3 Tutorial: Creating a Slideshow Last thing to do is to handle the actual motion.
private function animate(e:TimerEvent):void { // don't do anything if there isn't at least one image if (loadedItems<1) return; // check if there is a "next" image to display if (container.numChildren > displayedItems) { var picToShow:Bitmap = Bitmap(container.getChildAt(displayedItems)); var targetX:int = stage.stageWidth/2; var itemX:int = picToShow.x + picToShow.width/2 + container.x; } else { var delta:int = (itemX-targetX) / ANIMATION_SPEED_FACTOR;

by Armand Niculescu

// the next image hasn't loaded yet, keep showing this one var picToShow:Bitmap = Bitmap(container.getChildAt(displayedItems-1)); var delta:int = 0;

// the image has stopped... if (delta<=0) { // the next image should be displayed... if (((getTimer()-lastTime) >= ANIMATION_STILL_TIME * 1000) && (loadedItems >= displayedItems)) { lastTime = getTimer(); displayedItems++; titleField.text = ""; } else { // we should display the title for the current image if (titleField.text == "" && titles[container.getChildIndex(picToShow)] != null) titleField.text = titles[container.getChildIndex(picToShow)]; } } // the image is still moving else { // check if we need to wrap around // conditions: we've already displayed all loaded items and the last image is now just at the left edge if ((displayedItems==loadedItems) && ((container.x + container.getChildAt(displayedItems-1).x) <=0)) { var diff:int = 0 - (container.x + container.getChildAt(displayedItems-1).x); // seamlessly move the container back at the beginning and reset displayed images container.x = container.getChildAt(container.numChildren-1).width - diff; displayedItems = 0; } // move the container to the left container.x -= delta; } }

The code should be pretty easy to follow, especially if you read the comments. The only tricky part is the wrap-around. The condition for wrap-around is when the last loaded image was in center and its left edge has just come off the left side of the Stage. To make it easier to follow, I made this diagram.

2008 MediaDivision.com

10

AS3 Tutorial: Creating a Slideshow

by Armand Niculescu

The condition (container.x + container.getChildAt(displayedItems-1).x) <=0) becomes clear. When displayedItems==loadedItems, displayeditems-1 is the same as loadedItems-1, the last image loaded (newbies only: remember that in flash, all indexes are zero-based, if there are n elements, they lay within the interval [0..n-1]). The x position of this last image relative to its parent (container) is always the same, but the x position of the container itself changes, so we calculate the x position of image relative to the Stage. When this position becomes zero or less, we move the container back at the beginning, taking in consideration the width of the duplicated image. container.getChildAt(container.numChildren-1) returns a reference to the last image in container, which is not the last loaded image, but the last duplicated image. The x position of this duplicated image is negative (the first loaded image starts at x=0) so that's why the container.x has to be adjusted to its width.

Finishing touches
At this point, the code works. I will add just two small features to it: A shuffle feature: if there are many images in the xml list, it makes sense to shuffle their order each time. The code is very simple:
private function shuffle(arr:Array):Array { var shuffled:Array = arr.slice(); for (var i:int=0; i<arr.length; i++) { var element:Object = shuffled[i]; var rnd:int = Math.floor(arr.length * Math.random()); shuffled[i] = shuffled[rnd]; shuffled[rnd] = element; } return shuffled; }

The code is very simple, I won't bother you with details, except for one thing: arr.slice() creates a clone of the original array, so that we can shuffle the copy of the array without affecting the original.

2008 MediaDivision.com

11

AS3 Tutorial: Creating a Slideshow To use it, define a new constant


private const RANDOMIZE_IMAGES:Boolean = true;

by Armand Niculescu

and then, in the onDataLoaded function, add:


private function onDataLoaded(e:Event):void { if (!xmlLoader.data) { trace("XML Load failed"); return; } var dataXml:XML = XML(xmlLoader.data); for each (var item:XML in dataXml.item) { queue.push({url:item.@src, title:item.@title}); } if (RANDOMIZE_IMAGES) queue = shuffle(queue); } loadNextItem();

The other thing is using an embedded font for the title. For this, in the flash movie, open the Library panel, right-click on it and choose New Font. For Name, write TitleFont and for Font, choose a font from your system. Then, right-click on the font in the Library and choose Linkage. Write Class: TitleFont and Base Class: flash.text.Font and then check Export for Actionscript and Export in First Frame. Now go back to where you create the textfield (in the onAddedToStage function) and replace the old code for creating and setting the text field with this new one:
titleField = new TextField(); titleField.x = 0; titleField.width = stage.stageWidth; titleField.y = stage.stageHeight - 30; titleField.height = 30; titleField.selectable = false; titleField.embedFonts = true; titleField.antiAliasType = flash.text.AntiAliasType.ADVANCED; var format:TextFormat = new TextFormat(); format.font = new TitleFont().fontName; format.align = TextFormatAlign.CENTER; format.size = 20; titleField.defaultTextFormat = format; titleField.text = "";

Final code
We're done now. The full source code follows:
/************************************************************************************************** * AS3 SLIDESHOW v1.1 | 2008.03.30 | 2008 MediaDivision.com * * Licensed as GNU/GPL http://www.gnu.org/copyleft/gpl.html */ { package import import import import import /** flash.display.*; flash.events.*; flash.net.*; flash.text.*; flash.utils.*;

2008 MediaDivision.com

12

AS3 Tutorial: Creating a Slideshow

by Armand Niculescu

* Used to create a slideshow of dynamically loaded pictures, * based on an xml file */ public class Slideshow extends Sprite { // spacing between pics private const PIC_SPACING:int = 2; // time to display one picture private const ANIMATION_STILL_TIME:int = 4; // slide-in "braking factor"; smaller numbers result is faster braking; 1=instant braking; private const ANIMATION_SPEED_FACTOR:int = 6; // delay between animation steps in milliseconds private const ANIMATION_INTERVAL:int = 50; // randomize images in list? private const RANDOMIZE_IMAGES:Boolean = true; private var queue:Array = []; private var titles:Array = []; private private private private private private private private private var var var var var var var var var loader:Loader; xmlLoader:URLLoader; datasource:String; animator:Timer; xPos:int; lastTime:int; loadedItems:int; displayedItems:int; crtTitle:String;

private var container:Sprite; private var titleField:TextField; /** * Initialize the Slideshow * @param datasouce full url of the XML containing the */ public function Slideshow(datasource:String) { container = new Sprite(); addChild(container); this.datasource = datasource; xmlLoader = new URLLoader(new URLRequest(datasource)); xmlLoader.addEventListener(Event.COMPLETE, onDataLoaded); addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); } /** * Executes when the instance is actually added to Stage. * Only then we can access properties like stage.stageWidth */ private function onAddedToStage(e:Event):void { // disable stage scaling stage.scaleMode = StageScaleMode.NO_SCALE; // move container off-screen container.x = stage.stageWidth; // create the title field titleField = new TextField(); titleField.x = 0; titleField.width = stage.stageWidth; titleField.y = stage.stageHeight - 30; titleField.height = 30; titleField.selectable = false; titleField.embedFonts = true; titleField.antiAliasType = flash.text.AntiAliasType.ADVANCED; // text format for the title var format:TextFormat = new TextFormat(); //format.font = "Verdana"; // TitleFont class is created automatically by Flash CS3 IDE when you create a new Font in library // and you use the Linkage to specify a class. This way you can reference a font by class throughout // the project instead of its real name, i.e. "Futura Lt Bt" or "Century Gothic"

2008 MediaDivision.com

13

AS3 Tutorial: Creating a Slideshow


format.font = new TitleFont().fontName; format.align = TextFormatAlign.CENTER; format.size = 20; titleField.defaultTextFormat = format; titleField.text = ""; addChild(titleField); // create the animation timer animator = new Timer(ANIMATION_INTERVAL); animator.addEventListener(TimerEvent.TIMER, animate); animator.start(); } /** * Called when the XML has loaded */ private function onDataLoaded(e:Event):void { if (!xmlLoader.data) { trace("XML Load failed"); return; } var dataXml:XML = XML(xmlLoader.data); for each (var item:XML in dataXml.item) { queue.push({url:item.@src, title:item.@title}); } if (RANDOMIZE_IMAGES) queue = shuffle(queue); } loadNextItem();

by Armand Niculescu

/** * Retrieves an item from the loading queue and starts loading it */ private function loadNextItem():void { // get the first item in queue var item:Object = queue.shift(); // stop when the queue is empty if (item==null) return; // keep this until the image has loaded, so we can assign crtTitle = item.title; // initialize the loader for each load request loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.INIT, onItemLoaded); loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onItemError); loader.load(new URLRequest(item.url));

/** * Called when an image item has successfully loaded * @param e event */ private function onItemLoaded(e:Event):void { trace("loaded "+crtTitle); loadedItems++; var content:Bitmap = Bitmap(loader.content); content.x = xPos; xPos += content.width + PIC_SPACING; container.addChild(content); titles.push(crtTitle); if (queue.length>0)

2008 MediaDivision.com

14

AS3 Tutorial: Creating a Slideshow


loadNextItem(); else {

by Armand Niculescu

//duplicate first and last items for wrap-around-effect var refFirst:Bitmap = Bitmap(container.getChildAt(0)); var refLast :Bitmap = Bitmap(container.getChildAt(container.numChildren-1)); // sadly, duplicateMovieClip is gone so we use this; // thank God it's not a MovieClip we needed to duplicate var copyFirst:Bitmap = new Bitmap(refFirst.bitmapData); var copyLast:Bitmap = new Bitmap(refLast.bitmapData); copyLast.x = -copyLast.width - PIC_SPACING; copyFirst.x = xPos; container.addChild(copyFirst); container.addChild(copyLast); } }

/** * Called when an image item could not be loaded * Simply ignores the error and moves on * @param e event */ private function onItemError(e:Event):void { trace("load error"); loadNextItem(); } /** * Scrolls the loaded images from right to left; * If an image is not loaded, the animation will pause * Each image will stay still for a determined amount of time, displaying its title * When the slideshow reaches the end, the "strip" is wrapped around to the beginning * @param e the timer event */ private function animate(e:TimerEvent):void { // don't do anything if there isn't at least one image if (loadedItems<1) return; // check if there is a "next" image to display if (container.numChildren > displayedItems) { var picToShow:Bitmap = Bitmap(container.getChildAt(displayedItems)); var targetX:int = stage.stageWidth/2; var itemX:int = picToShow.x + picToShow.width/2 + container.x; } else { var delta:int = (itemX-targetX) / ANIMATION_SPEED_FACTOR;

// the next image hasn't loaded yet, keep showing this one var picToShow:Bitmap = Bitmap(container.getChildAt(displayedItems-1)); var delta:int = 0;

// the image has stopped... if (delta<=0) { // the next image should be displayed... if (((getTimer()-lastTime) >= ANIMATION_STILL_TIME * 1000) && (container.numChildren > displayedItems)) { lastTime = getTimer(); displayedItems++; titleField.text = ""; } else { // we should display the title for the current image if (titleField.text == "" && titles[container.getChildIndex(picToShow)] != null) titleField.text = titles[container.getChildIndex(picToShow)];

2008 MediaDivision.com

15

AS3 Tutorial: Creating a Slideshow

by Armand Niculescu

} } // the image is still moving else { // check if we need to wrap around // conditions: we've already displayed all loaded items // and the last image is now just at the left edge if ((displayedItems==loadedItems) && ((container.x + container.getChildAt(displayedItems-1).x) <=0)) { var diff:int = 0 - (container.x + container.getChildAt(displayedItems-1).x); // move the container back at the beginning and reset displayed images container.x = container.getChildAt(container.numChildren-1).width - diff; displayedItems = 0;

} }

// move the container to the left container.x -= delta;

} }

/** * Shuffle array elements * @param arr original array * @return shuffled array */ private function shuffle(arr:Array):Array { var shuffled:Array = arr.slice(); for (var i:int=0; i<arr.length; i++) { var element:Object = shuffled[i]; var rnd:int = Math.floor(arr.length * Math.random()); shuffled[i] = shuffled[rnd]; shuffled[rnd] = element; } return shuffled; }

Improving and extending


Like I noted earlier, this Slideshow is optimized for the cases where the width of images is large enough so that 2-3 images will be displayed at any time. If the images are too narrow or too few or the Stage too wide, the slideshow may fail. By changing te rules a bit, this can be overcome. Currently, the images flow right-to-left. It is possible to make the Slideshow scroll in both directions. You can also add a reflective floor effect for loaded images by duplicating and mirroring each clip and applying a gradient mask to it. Finally, and more importantly, you can add some interactivity, allowing the images to be clickable, taking you to a different URL, or maybe displaying more details. The possibilities are endless, so have fun!

Downloading
You can download the complete source code from: http://www.richnetapps.com/download/slideshow/slideshow.zip

2008 MediaDivision.com

16

Anda mungkin juga menyukai