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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | package { import flash.display.*; import flash.events.*; import flash.net.*; import flash.text.*; public class Slideshow extends Sprite { private const PIC_SPACING:int = 2; private var queue:Array = []; 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); } 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)); // 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); } } 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.
On the next page, we’ll see how to handle more events.
I’m having a serious problem that’s just recently appeared. my flash cs3 is unable to build a classpath to the external actionscript in your tutorial example as well as others I’ve downloaded tonight. it’s driving me crazy. have you ever encountered this?
my program was corrupted. had to re-install from scratch. fine now!
thanks for this terrific example….
This was dead on and will really help out. Thanks for the time and effort to detail the code. Mahalo!
Spot on. Very useful. Thanks
This is great I’m new to actionscript and this was very helpful. It looks really nice. I was wondering, how do i go about making the images ‘clickable’?
Thanks
Excellent presentation Armand. I
Awesome flash based slide show, great work explaining it step by step as well. Thanks for sharing, keep up the good work.
Thank you very much for the tutorial, especially the pdf and full code. I linked you to where I will use it and cite for credit. Thanks!
Excellent tutorial, Armand. I will share this link with my design students. Thanks much.
This is the best tutorial/example I’ve found on the subject.
One thing you could do different is to keep the image source and title strings directly in the XML object, since it is already loaded in memory, no reason to create another copy of it in memory. A slideshow is a linear arrangement but frequently the user can jump around so it isn’t necessary to have things in linear order. You end up using the id names. If you have a large number of images, say your slideshow totals into the 100’s of megs, it may be better to load them on demand, not all at once.