Join for FREE | Take the Tour Lost Password?
[x]

deviantART

 

Actionscript 3.0 Preloader by @summaro:iconsummaro:


Creative Commons License
Some rights reserved. This work is licensed under a
Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 License.
:iconsummaro:

Artist's Comments

Tutorial: Reusable Actionscript 3.0 Preloader

In the big Internet world where you can't guarantee the bandwidth of your visitors' Internet connections, publishing large files carries with it certain risk. In Flash, you can offset this risk somewhat by providing a preloader. A preloader is a little graphic/animation that you place in your Flash movies to tell the visitor that, yes, something is going on in their browser. Without apreloader, it often looks like your swf has crashed, or doesn't do anything.
Creating a preloader isn't hard, but it's something everyone involved with Flash will need to learn how to make at one time or another. This tutorial will show you how to make a reusablepreloader that is completely customizable, so that you only need to ever write one, and then just need to import it and use it when you need it. Thispreloader can be used to load the entire movie at once, or it can be used to load individual components of your swf.

We'll start by looking at our needs from this preloader. We want a preloader that:

* Can be included in any type of project.
* Can be used more than once per swf file.
* Can be used for the entire swf as well is individual swf components.
* Can be completely customized without significantly rewriting the code.


To do this, we will be using 3 files:

1. Preloader.as - This class contains all the logic for handling preloading and attaching to objects
2. IPreloaderClip.as - This interface (we'll cover interfaces later on) defines the contract that all preloader graphics classes must follow.
3. PreloaderGraphic.as - This class contains the visual look of our preloader and implements IPreloaderClip.


The rational behind our design is as follows:
Considering that we want the preloader to be fully customisable and not restricted in any way by appearance, it's logical that we want to split the look of the preloader and the function of the preloader into 2 different classes. That way, when we want to change the look of the preloader, we don't have to rewrite the code used to attach the preloader and to track the bytes loaded. To make sure our preloader graphic can interact with our preloader class, we use the IPreloaderClip interface to define the contract.

For those of you who are not familiar with an interface, as has been said a few times, an interface is a sort of contract between classes. An interface contains function definitions (function myFunction():void), without implementations. That's pretty much all there is to them. Classes then implement this interfaces, and have to provide their own implementation of the interface definitions. The beauty of interfaces comes in when you write a class that is supposed to be able to interact with a number of classes that do the same thing, but in different ways (in the case of ourpreloader, the preloader is supposed to be able to interact with preloader graphics, which all somehow show progress, just in different ways).
When you pass in a parameter, you specify it's type, and the mxmlc compiler makes sure that you're passing in compatible types. This introduces a high cohesion/coupling between classes - for example, if we tell thePreloader class it's expecting a PreloaderClip, we can only ever have one class called PreloaderClip, and if we want it to look differently for different situations, we have to do some pretty ugly code acrobatics.
However, if we instead specify the interface IPreloaderClip, and the mxmlc compiler will check to see if the class we pass in implements the interface, and we're not tied to a specific class. This means our classes are more loosely coupled, and ourPreloader can act more polymorphically (Wikipedia). This means we can create many Preloader graphics with different code and the Preloader will be able to treat it as if it were the same class, thanks to the interface.

Lets get into the code:

First, we'll have a look at the interface definition for IPreloaderClip:


package preloader
{
    import flash.events.ProgressEvent;
    import flash.events.IEventDispatcher;

    public interface IPreloaderClip extends IEventDispatcher
    {
        function onProgress(e:ProgressEvent):void
    }
}


This is in the file IPreloaderClip inside a folder called "preloader".
As you can see, we import 2 classes - ProgressEvent and IEventDispatcher. ProgressEvent is an event class that is dispatched by the LoaderInfo class and provides a means for tracking how many bytes have loaded and how many bytes the object is in total. IEventDispatcher is an interface implemented by every object that can dispatch events.
Next, we have the interface declaration, declaring the interface itself and extending from IEventDispatcher. Then we have a definition for the onProgress function, which takes a ProgressEvent class as it's only parameter.
What this is declaring is an interface with a single method, onProgress, which inherits the interface definition of IEventDispatcher. This means that all objects that implement this interface need to have an onProgress function, and need to be able to dispatch events, whether it's through inheriting the EventDispatcher class (or a child of that class), or explicitly defining it's own functionality.

If you've not done much Actionscript 3.0 programming, it is a language based heavily on an event dispatch system. Objects that inherit from EventDispatcher, or implement IEventDispatcher, are capable of dispatching events. Events are custom classes that contain information about what's happening to the object that dispatched the event. You can attach event listeners toEventDispatchers to handle these events as they happen. You'll see an example of this next.

This interface serves as our contract, and lets our Preloader class know that the Preloader graphics clip it's been passed is valid.

Lets take a look at the Preloader class:


package preloader
{
    import flash.display.*;
    import flash.events.*

    public class Preloader extends MovieClip
    {
        public static const LOADED:String = "loaded";


        private var loader:EventDispatcher;

        private var graphic:IPreloaderClip


        public function Preloader(loading:EventDispatcher, clip:IPreloaderClip)
        {
            loader = loading;
            graphic = clip;
            if (graphic is DisplayObject)
                addChild(DisplayObject(graphic));
            loader.addEventListener(ProgressEvent.PROGRESS, graphic.onProgress);
            graphic.addEventListener(Preloader.LOADED, onLoaded);
        }

        private function onLoaded(e:Event):void
        {
            loader.removeEventListener(ProgressEvent.PROGRESS, graphic.onProgress);
            loader.removeEventListener(Preloader.LOADED, onLoaded);
            if (parent.contains(this))
                parent.removeChild(this);
        }
    }
}


This class is in the file Preloader.as in the folder "preloader".
As you can see, we're defining the class for our preloader here.
The first thing you'll notice is the declaration for a private static constant named LOADED. A static variable is a variable that is available to ALL objects/instances of the class it's defined in, because it's tied to the class rather than a specific instance. As such, you access a static variable by writing the class name, then the variable name. In this instance, it would bePreloader.LOADED. This is common syntax for most event classes.
The const keyword means we're declaring a constant. A constant is a type of variable that is immutable. That is, once it's been set, it cannot be changed. Constants are useful to map out fundamental properties that aren't going to change.

The constructor passes in an EventDispatcher, and an IPreloaderClip as it's parameters. The first parameter "loading" is the object that is doing the actual loading for whatever we're loading. Because we want this class to be able to load entireswfs, individual components with no restrictions, we specify a generic EventDispatcher object as the parameter. That way, if we choose to attach it to a swf file, we can use the MovieClip.loaderInfo property to get the LoaderInfo object for the swf, or the Loader.contentLoaderInfo property to get the LoaderInfo object for any image based external content we're loading, or the URLLoader object for any binary data we're loading.
The second parameter is our interface we defined earlier, and is the specific implementation of our preloader graphic. The preloader graphic actually does all the heavy lifting - the Preloader class just wires everything up, when it attaches the event listener to the loading object, and delegates it to the IPreloaderClip implementation.

We assign these 2 parameters to private class variables, so that we can access them from other methods in the class. Then, if the preloader graphic is a DisplayObject, we add it to the preloader's display stack. We attach 2 event listeners, which listen for events.
When these 2 events are dispatched, the class invokes the methods outlined in the attaching syntax. As mentioned earlier, when aProgressEvent is dispatched from our loading object, the preloader delegates it to the preloading graphic.
When the preloading graphic dispatches an event with the Preloader.LOADED type, we call onLoaded. As you can see, it's using the constant defined in this class to dispatch events - we'll cover dispatching custom events later.
When onLoaded is called, it removes these 2 event listeners, and then removes the preloader from it's parent's display stack, if it's been attached. This is to ensure that all references to these objects are removed so that they can be "garbage collected" and the resources they use can be freed up to be used elsewhere. If we didn't do this, we'd have a lot ofpreloader objects floating around taking up memory for no reason, and it could lead to degraded performance of our swf.

If you're unfamiliar with the concept of delegation, it's basically just passing on the execute of logic to an external function. In this case, thePreloader class delegates the event handling for the ProgressEvent to the implementation of the IPreloaderClip passed in - this way we can use whatever class we want to handle the event, as long as that class implements the IPreloaderClip interface.

Finally, we have our specific implementation of the IPreloaderClip interface, PreloaderGraphic


package
{
    import flash.display.MovieClip;
    import flash.events.*;
    import flash.utils.Timer;
    import preloader.*

    public class PreloaderGraphic extends MovieClip implements IPreloaderClip
    {
        public var size:int = 60;
        public var stroke:int = 15;
        public var colour:uint = 0;
        public var fadeoutTime:Number = 0.2;

        private var killTimer:Timer;

        public function PreloaderGraphic()
        {

        }

        public function onProgress(e:ProgressEvent):void
        {
            graphics.clear();
            graphics.lineStyle(stroke, colour, alpha);
            drawArc(0, 0, size / 2, 0, Math.floor((e.bytesLoaded / e.bytesTotal) * 360) / 360, 20);
            if (e.bytesLoaded >= e.bytesTotal)
                this.kill();
        }

        private function drawArc(centerX:Number, centerY:Number, radius:Number, startAngle:Number, arcAngle:Number, steps:Number):void
        {
            // Rotate the point of 0 rotation 1/4 turn counter-clockwise.
            startAngle -= .25;

            var twoPI:Number = 2 * Math.PI;
            var angleStep:Number = arcAngle/steps;
            var xx:Number = centerX + Math.cos(startAngle * twoPI) * radius;
            var yy:Number = centerY + Math.sin(startAngle * twoPI) * radius;
            graphics.moveTo(xx, yy);
            for(var i:int=1; i<=steps; i++)
            {
                var angle:Number = startAngle + i * angleStep;
                xx = centerX + Math.cos(angle * twoPI) * radius;
                yy = centerY + Math.sin(angle * twoPI) * radius;
                graphics.lineTo(xx, yy);
            }
        }

        private function kill():void
        {
            killTimer = new Timer(fadeoutTime * 10, 0);
            killTimer.addEventListener(TimerEvent.TIMER, killTick);
            killTimer.start();
        }

        private function killTick(e:TimerEvent):void
        {
            alpha -= 0.01;
            if (alpha <= 0)
            {
                killTimer.removeEventListener(TimerEvent.TIMER, killTick);
                parent.removeChild(this);
                dispatchEvent(new Event(Preloader.LOADED));
            }
        }
    }
}


This class lives in PreloaderGraphic.as, outside the "preloader" folder (it has an anonymous package declaration).
While this class might look large and a little daunting, looking at it a little closer, we find out it's mostly just support code.

The drawArc, kill, and killTick functions are functions just to support the graphical implementation of our preloader, and we'll get to these later.

As you can see, the class implements the IPreloaderClip interface, and as such, requires an implementation of onProgress (remember the Preloader class delegates it's event handling for the ProgressEvent to this function).
The onProgress method, in a nutshell, clears the graphics object for the preloader graphic, and draws an arc, as a function of the proportion of 360 degrees by the bytes loaded/bytes total ratio. What this means is, it calculates a fraction that represents how much of the movie is loaded at a given time, for example, 0.5, which is half. It then multiplies this by 360 to get 180, and draws an arc that's 180 in angle.
You'll find when you compile and run it, as the object loads, it draws out a circle.

The important part of this function is the e.bytesLoaded and e.bytesTotal, which are properties of the ProgressEvent class. e.bytesLoaded is, as you may have guessed, the number of bytes currently loaded of the object you're loading, and likewise, e.bytesTotal is the total bytes to be loaded.
These are the two properties you will concentrate on to provide feedback in your preloader. Your preloader might just be text, in which case, you'd just create a text field and update the text with the percentage of bytes loaded ((e.bytesLoaded / e.bytesTotal) * 100). Or, you might choose to do a loading bar, where you'd have a square, and you'd expand the fill to take up a percentage of the available space using a similar method.
In this preloader, we draw a circle based on how many bytes have loaded. When we've loaded all the bytes, we call the kill function, which attaches a timer which ticks every 10 milliseconds, and callskillTick, which reduces the alpha of the preloader. This will result in the preloader disappearing in about 1 second, and when it's alpha is 0, it stops the timer, removes the graphic from the stage and finally dispatches a custom event.

In Actionscript 3.0, you can dispatch a custom event by simply creating a new instance of the Event class. This class requires a single parameter, which is the name of the type of event. This is a string argument, but Flash convention states that this is usually a constant somewhere, to avoid hard coded values in your code. This way, if you need to change the actual value of the event type, you don't need to hunt it down in all yourpreloader graphic classes - just change the constant we defined earlier.
The event dispatch/handling model of Flash is outside the scope of this tutorial, and is covered quite well in many Actionscript 3.0 books. If you want to know more, picking up one of these would be well worth your time.

So, that's the general package and class structure of our reusable preloader. But how do you actually use it? Consider the following driver file:


package
{
    import flash.display.MovieClip;
    import flash.display.Loader;
    import flash.net.URLRequest;
    import preloader.Preloader;

    public class PreloaderDriver extends MovieClip
    {
        public function PreloaderDriver():void
        {
            var loader:Loader = new Loader();
            var req:URLRequest = new URLRequest("test.jpg");
            loader.load(req);
            addChild(loader);

            var preloader:Preloader = new Preloader(loader.contentLoaderInfo, new PreloaderGraphic());
            preloader.x = 200;
            preloader.y = 200;
            addChild(preloader);
        }
    }
}


This is an anonymous package, and so sits outside the Preloader folder. We create a driver class, extend MovieClip, and write our constructor. Our constructor creates a loader object and loads in a jpg file that's in the same directory as our driver, and it adds the loader to the stage display stack.
It then passes the loaders LoaderInfo object (available through the contentLoaderInfo property of the Loader object) to the preloader, and a new instance of our PreloaderGraphic (which is in the same folder and has an anonymous namespace, so we don't need to import it, remember). We position the preloader, and add it to the display stack.

Compile this and run it. Where's the preloader? All I saw was a circle fading fast. This is because the file is already on your local machine and so doesn't need to be downloaded. You have 2 options to test it; you can use the FlashIDE to do a download test, or you can upload it to a server and access it remotely.
What you should see is the circle slowly complete itself, then when the content loads, it fades away.

To customise this preloader, simply write new implementations of the IPreloaderClip interface.

Good luck, and have fun.

Comments


love 0 0 joy 0 0 wow 0 0 mad 1 1 sad 0 0 fear 0 0 neutral 0 0
:iconkolnstyle:
Are you trying to get a DD? =P
:iconsummaro:
No, just trying to spread the good OOP love.

--
The DataGrid control is intended for viewing data, and not as a layout tool like an HTML table - Adobe
:iconsummaro:
Thanks. :bucktooth:

--
The DataGrid control is intended for viewing data, and not as a layout tool like an HTML table - Adobe
:iconsummaro:
More to come, we're just getting into the fun stuff now!

--
The DataGrid control is intended for viewing data, and not as a layout tool like an HTML table - Adobe
:icondarkdothacker:
this is great, thanks

--
[link]
:iconkolnstyle:
awright!

I think I should write a tutorial too, something amazing, like motion and easing with AS 3.0.
:iconsummaro:
My pleasure.

--
The DataGrid control is intended for viewing data, and not as a layout tool like an HTML table - Adobe

Details

June 22, 2008
1.1 KB
1.7 KB
113×101

Statistics

54
31 [who?]
5,090 (1 today)
367 (0 today)

Site Map