V2 Architecture: From Class to Component

[ 2006-02-07 11:02:46 | Author: liuhuan ]
Font Size: Large | Medium | Small
By devonair From shavedplatypus.com

http://shavedplatypus.com/forum/viewtopic.php?t=1209

The purpose of this tutorial is to show how to make a simple (very simple) Flash component using Flash's so-called "v.2 architecture" style of component creation. Along the way, we'll also check out a few basic ideas of Object Oriented Programming (OOP). First though, let's take a quick look at what we'll be making. Doesn't seem like much (and it ain't) - just a simple slider that can be adjusted from 0 to any number you desire (for example, 100 to adjust volume or maybe 360 to rotate a movie clip). What makes this slider "special" is that we'll create it from scratch and ultimately compile it to a .mxp file to share with all your family and friends (portability being one of the basic tenements of OOP).

THE .FLA
Let's begin by making the graphics for our slider. We don't want anything too fancy here, as, it being a component, we want it to be able to fit into pratically any application environment imaginable. Ubiquity and Simplicity are the keywords to keep in mind for component design. You sure wouldn't want to design a component with a neon green glowing halo, for example (yeah, I'm talkin' to you, Macromedia).
In this case, I opted on a simple inner shadow effect created in Photoshop to give the component a "sunken" look.
-SIZE MATTERS-
If it isn't already a habit for you, becoming adept at Flash development will train you to think ahead. Consider it a game of chess. You always need to be at least two moves ahead or you'll wind up creating yourself a considerable amount of extra work. We want to make this component as simple as possible, so, planning ahead, we'll figure our "dragger" (that's what we'll call the little thing you drag from now on) will slide from an x value of 0 to a nice round x value of 100. That's a good start, but if we think for a moment where we plan to position the dragger movie clip's registration point, we realize it is bound to extend beyond those set values. For example, if we make our dragger movie clip 10 pixels tall, 15 pixels wide, and position it's registration point in the upper left hand corner (and we will), if positioned at an x value of 100 inside another movie clip, it will extend to 115 (100 plus its width of 15). For that very reason, I made my slider track 115 pixels wide. That is why size matters why thinking ahead can save some work. In general though, these graphics can look like anything you'd like. You can simply make some rectangles in Flash, or you can do something a little fancier in Photoshop. The point here isn't graphics construction, but component construction.
But enough talk, let's Flash. If it isn't already open, open Flash and create a new .fla. Insert a new movie clip named "slider track" and create your track graphic that's 115 pixels wide, 10 pixels tall and positioned at the registration point (0,0). Insert another movie clip named "dragger" and create some rectangular graphic 15 pixels wide, 10 pixels tall and, again, positioned at 0,0. Finally create a third movie clip named "Slider" and on two separate layers, drag in instance of your first two movie clips and position them both at 0, 0 [TIP: make use of the align tools with the "align to stage" button depressed - this makes short work of aligning movie clips to the registration point]. The track movie clip doesn't need to be named (though you can name it if you so desire). Give the dragger movie clip an instance name of "dragger_mc", though. Right click on the Slider movie clip in the library panel and bring up the clip's "linkage" dialogue box. Set the movie clip to export for actionscript in the first frame with the default name of "Slider". While the box is still open, also set the AS 2.0 Class to "Slider". This tells the movie clip the name of the class file from which it should get its script - a class file we'll begin writing right now. Save this .fla as "Slider.fla" and let's move on.

THE CLASS
Create a new .as file and save it as "Slider.as" inside the same directory as your "Slider.fla" file. It's customary to put information regarding your class file (author, usage, version, etc.) in a header at the top of the class file. Obviously, this isn't necessary, but it's a good practice, so let's go ahead and type up a quick header:

Code:
/**********
Slider class returns a value between 0 and a specified ending value as the user drags the little bar

Version: 1.0
Author: Devon O.
Date: 25JAN06
**********/

or some such junk. Go ahead and throw your own name in there - I won't care.

Classes always begin with the keyword "class" (logically enough). In this case we also know the class is governing (i.e. "extending") the behavior of a movie clip. So to start out our class, we simply write:

Code:
class Slider extends MovieClip{
}

So, for those familiar with OOP, our Slider class will inherit all the properties and methods of the MovieClip class. For those not hip to OOP, the following is a very brief intro to classes and OOP in general. You can skip the next section if you'd like.

CLASSES
Classes are the building blocks of Object Oriented Programming languages. "Objects", in fact, are specific instances of generalized classes. All classes contain properties and methods that allow instances of that class to interact with instances of other classes and, in the process, build up a functional program. The code inside a class is entirely self contained (i.e. "encapsulated") so that a programmer using the class doesn't need to know how it works in order to put it to good use. Let's take a look at a class I'm sure everyone is, at least, generally familiar with: the MovieClip. The MovieClip has properties (which can be thought of as variables inside a class) such as "_width" and "_rotation" and it has methods (which can be thought of as functions within a class) such as "createEmptyMovieClip()" and "getNextHighestDepth()". Thanks to encapsulation, you may have no idea what the MovieClip class looks like, but you (should) know enough to get it to do something when you click on it. Speaking of which, classes also contain things called event handlers such as "onRelease" and "onLoad" which allow them to communicate with other objects in your program. Although you may use something like my_mc.onRelease (for example) everyday, you may not (or may) have been aware that what you are, in essence, saying is:

Code:
someObject.onSomeTriggeredEvent = function(){
thisObject.communicateSomeInformationTo(someOtherObject or itself);
}

That is what Object Oriented Programming is all about. We'll get back to event handlers in a little bit. First though, let's write some script. [NOTE: you may have noticed that I tend to capitalize the names of classes such as MovieClip or Slider. While not necessary, this is a common convention in OOP languages like ActionScript. You can find other naming conventions in the handy dandy Flash help file or here: http://livedocs.macromedia.com/flash/8/main/00001641.html]

THE SCRIPT
Time to start thinking ahead again. Now that we know a little bit about classes, it's time to start considering what properties and methods our Slider class should possess. Well, already know our slider contains a movie clip named "dragger_mc", so that property's a given. We'll also need a variable/property that determines the maximum value our slider should reach. We'll just call that "maxVal". Still thinking ahead, we may want to set the initial value of our dragger (for example, if we use our slider as a volume controller, we may want it to start with a value of 50 let the user turn the volume up or down from there). So, we'll throw in aproperty called "initVal". Possibly the most important property is the current value of the slider's dragger position, so we'll also add a property named "curVal".
Now, what methods do want our slider to have? Methods, keep in mind, are basically the actions instances of the class can perform. Looking at our properties, we can see that we'll want to be able to set the maximum and inital values, so we'll have two methods "setMaxValue()" and "setInitValue()". Of course we'll want to be able to retrieve the the slider's current value at any given time so we'll add another method called "getValue()". Every class also has a constructor method which has the same name as the class, itself. This method is called the moment an instance of the class is created (i.e. "instantiated"), or the component (or movie clip) appears on stage. This means we will also have a "Slider()" method.
Now, let's return to the idea of event handlers as promised earlier. We want our component to communicate changes in it's state to other objects by "broadcasting" a predefined message when that change takes place. In fact, we'll have our component broadcast the message "onChanged" to any object that may be listening to it whenever an end user adjusts the position of the dragger. This, of course, implies the concept of a "listener" object. I won't go into any detail about listeners and broadcasters here. I'll just say, the easiest way to add such functionality to a class is by using Flash's AsBroadcaster class. Again, I won't go into detail about the inner workings of this class, but suffice it to say, by calling its "initalize()" method, it allows other classes to add and remove listeners and broadcast custom messages. Using this class though, forces us to add three additional properties to our own Slider class - namely, the functions (read methods) "addListener()", "removeListener()", and "broadcastMethod()".
Finally, then, we can begin putting this all together. Our list of properties now looks like this:

Code:
// ***** PROPERTIES *****
// private properties - only accessible from within the class, not to the general user.
// It's usually good practice to keep properties private.
private var dragger_mc:MovieClip;
private var curVal:Number;
private var maxVal:Number;
private var initVal:Number;
//
// properties necessary for AsBroadcaster use
public var addListener:Function;
public var removeListener:Function;
private var broadcastMessage:Function;

The easiest methods to write will be our getters and setters [NOTE: Flash supports "actual" get and set methods, but I won't be using those here. Basically they are a way of using public methods to manipulate private properties while appearing to the casual user as though they are manipulating those properties directly], so we'll get them out of the way first.
Our two setter methods (which will be accessible by the general user, i.e. "public") will simply adjust our maxVal and initVal properties and will look like this:

Code:
public function setMaxValue(val:Number):Void {
maxVal = val;
}
public function setInitValue(val:Number):Void{
initVal=val;
}

Our getter method will return a number (namely, the value of our curVal property), so we'll be sure to type it accordingly.

Code:
public function getValue():Number {
return curVal;
}

Now, the tricky part - the constructor method. Our constructor should define any undefined properties, call the AsBroadcaster "initialize()" method, register itself (i.e. the instance of the Slider class) as its own listener, then call the simple, but very important, private method which will initialize our dragger_mc actions (the meat and potatoes of our class). [NOTE: constructor methods are not typed as what they return is an instance of the very class they are defining].

Code:
function Slider() {
// set our default maxVal property to 100
if (maxVal == undefined) maxVal = 100;
// set our default initVal property to 0
if (initVal == undefined) initVal = 0;
// pass this object to the AsBroadcaster initialize() method and register it as a listener to itself
AsBroadcaster.initialize(this);
this.addListener(this);
// call the function to initialize our "dragger_mc"
initDragger();
}

And our last method is the one that actually gets our Slider class to "work". Our "initDragger()" method will correct any silly user mistakes, then set up the actions of the dragger (which will drag when pressed and broadcast the message "onChanged" when moved).

Code:
private function initDragger():Void {
// create a reference to this object
var thisObj:Slider = this;
// if a user sets the maxVal property to a fraction, round the fraction off
if (maxVal%1) maxVal = Math.round(maxVal);
// if a user sets the maxVal to 0 or a negative number, just make the maxVal 1
if (maxVal<1) maxVal = 1;
// if a user sets the initVal to outside the range of the slider, make the initVal 0
if (initVal<0 || initVal>maxVal) initVal = 0;
var ratio:Number = maxVal/100;
dragger_mc._x=initVal/ratio;
dragger_mc.onPress = function() {
this.onMouseMove = function() {
thisObj.curVal = Math.round(this._x*ratio);
thisObj.broadcastMessage("onChanged");
updateAfterEvent();
};
this.gotoAndStop("down");
this.startDrag(false, 0, 0, 100, 0);
};
dragger_mc.onRelease = dragger_mc.onReleaseOutside=function () {
delete this.onMouseMove;
this.gotoAndStop("up");
this.stopDrag();
};
}

If you happen to read through that method carefully, you'll notice that the dragger_mc's actions are dependent on the values of our maxVal and initVal properties. For this reason, any time those values are changed, we'll need to call the initDragger() method again. So, we now need to go back to our "setMaxVal()" and "setInitVal()" methods and add the line:

Code:
initDragger();

That's what happens when you don't think ahead. If we put all that together, then, we come up with this final Slider class:

Code:
//
/**********
Slider class returns a value between 0 and a specified ending value as the user drags the little bar

Version: 1.0
Author: Devon O.
Date: 25JAN06
**********/
//
class Slider extends MovieClip {
// ***** PROPERTIES *****
// private properties - only accessible from within the class, not to the general user.
// It's usually good practice to keep properties private.
private var dragger_mc:MovieClip;
private var curVal:Number;
private var maxVal:Number;
private var initVal:Number;
//
// properties necessary for AsBroadcaster use
public var addListener:Function;
public var removeListener:Function;
private var broadcastMessage:Function;
//
// constructor
//
function Slider() {
// set our default maxVal property to 100
if (maxVal == undefined) maxVal = 100;
// set our default initVal property to 0
if (initVal == undefined) initVal = 0;
// pass this object to the AsBroadcaster initialize() method and register it as a listener to itself
AsBroadcaster.initialize(this);
this.addListener(this);
// call the function to initialize our "dragger_mc"
initDragger();
}
//
// private method
//
private function initDragger():Void {
// create a reference to this object
var thisObj:Slider = this;
// if a user sets the maxVal property to a fraction, round the fraction off
if (maxVal%1) maxVal = Math.round(maxVal);
// if a user sets the maxVal to 0 or a negative number, just make the maxVal 1
if (maxVal<1) maxVal = 1;
// if a user sets the initVal to outside the range of the slider, make the initVal 0
if (initVal<0 || initVal>maxVal) initVal = 0;
var ratio:Number = maxVal/100;
dragger_mc._x=initVal/ratio;
dragger_mc.onPress = function() {
this.onMouseMove = function() {
thisObj.curVal = Math.round(this._x*ratio);
thisObj.broadcastMessage("onChanged");
updateAfterEvent();
};
this.gotoAndStop("down");
this.startDrag(false, 0, 0, 100, 0);
};
dragger_mc.onRelease = dragger_mc.onReleaseOutside=function () {
delete this.onMouseMove;
this.gotoAndStop("up");
this.stopDrag();
};
}
//
// public methods
//
public function getValue():Number {
return curVal;
}
public function setMaxValue(val:Number):Void {
maxVal = val;
initDragger();
}
public function setInitValue(val:Number):Void{
initVal=val;
initDragger();
}
}

Save that (if you haven't been saving frequently all along - and if you haven't been, shame on you), then return to the .fla file.

TESTING THE CLASS
Drag an instance of the "Slider" movie clip from your library to the stage, give it an instance name of "mySlider", and in a new layer add the script:

Code:
mySlider.setInitValue(50);
mySlider.onChanged = function() {
trace(this.getValue());
};

Test the movie and see what happens. Notice how your dragger is immediately in the center of the track graphic we made. Now, drag it back and forth and see how Flash traces from 0 to 100 as you move from one end to the other. Keep in mind, instead of tracing, you could just as easily have said:

Code:
someSound.setVolume(this.getValue());

and had yourself a volume control for .mp3 player. Try this script and test again:

Code:
mySlider.setMaxValue(500.25);
mySlider.setInitValue(-50);
mySlider.onChanged = function() {
trace(this.getValue());
};

Pretty bitchin', huh. Play around a bit and see what else you can do. It'll be a welcome diversion from all the hard work. Really, at this point, if you didn't want to play nice and share, you could call it a day. You've got a perfectly good, functioning, OOPy, Slider class on your hands that you could add to any application you wanted. Being the nice guy you are, though, you want to share this class with all your collegues and coworkers but don't want to go through the hassle of passing around .fla and .as files. That's why there are components.

THE COMPONENT
To take a break from coding, let's do a little more graphics work. In Photoshop or whatever you may use to create .png files, make a 18 pixel by 18 pixel image depicting the slider class and save it as "icon.png". It doesn't have to be anything fancy (and at 18 x 18 pixels really can't be), but this is the image that will be displayed in the components panel once our component has been compiled and installed.
Probably the most useful thing about components next to how easy they are to distribute and use in any application (i.e. their "portability") is how a user can set property values within Flash's parameters panel without needing to write a single line of script. To do this, we will need to add "metadata" to our class file. Metadata in class files just a way to let Flash know how you'd like your component to behave. For this simple component, the only metadata we will trouble ourselves with are the "Inspectable" and "IconFile" tags. The IconFile tag is fairly self explanatory. It simply tells Flash what file to use as the component's icon. Back in our class file, Slider.as, after the informative header and before the class declaration add this little bit of metadata:

Code:
[IconFile("icon.png")]

It doesn't get any easier than that.
The "Inspectable" metadata tag tells flash that the immediately following property should be seen ("inspected") in the parameters panel and lets the coder add a few personalizing touches as to how the property appears. So, back in Slider.as, just above the "maxVal" property line add:

Code:
[Inspectable(defaultValue=100, name="Maximum Value")]

This tells flash that our maxVal property should be able to be set in the parameters panel (though we'll still be able to use our setMaxValue() method) and it should default to a value of 100. The "name" attribute just Flash what to display in the parameters panel. Without it, Flash will just show the name of the property. In this case it would display "maxVal", which may not be as clear as spelling out "Maximum Value". Likewise, just above the "initVal" property add the metadata:

Code:
[Inspectable(defaultValue=0, name="Initial Value")]

The top portion of our class file should now look like this:

Code:
[IconFile("icon.png")]
class Slider extends MovieClip {
// ***** PROPERTIES *****
// private properties - only accessible from within the class, not to the general user.
// It's usually good practice to keep properties private.
private var dragger_mc:MovieClip;
private var curVal:Number;
// properties that appear in the parameters panel
[Inspectable(defaultValue=100, name="Maximum Value")]
private var maxVal:Number;
[Inspectable(defaultValue=0, name="Initial Value")]
private var initVal:Number;
//
// properties necessary for AsBroadcaster use
public var addListener:Function;
public var removeListener:Function;
private var broadcastMessage:Function;
//
// constructor
//
...

Back in the .fla, delete your actionscript layer, and the slider movie clip from the stage. In the library panel, right click on the Slider movie clip and open the "Component Definition" dialogue box. In the AS 2.0 Class box type in "Slider" (with no quotes) and click OK. And *poof* just like magic, there is your component complete with custom icon [NOTE: Okay, at this point, for some reason, in order to get my icon to appear, I had to open up the linkage dialogue box again, and, without making any changes, just click okay. Just a little glitch, nothing to worry about].
Drag an instance of the component onto the stage and check the parameters panel. Amazing. Play around a bit just to see if it's working. We're nearly home now.

THE .MXP
The simplest way to share your components with others is to package it inside a .mxp file. The .mxp file can be thought of as an archive file that will install the component automatically according to a specific set of instructions. This set of instructions is contained inside a .mxi file. Don't let the name scare you off. All a .mxi file is, is a strictly formatted .xml file with a fancy extension. They can be created or edited with any .txt or .xml file editor. Here is a blank template modified from one from Macromedia that you can feel free to save and use whenever you wish:

Code:
<macromedia-extension name="NAME OF YOUR COMPONENT" version="COMPONENT VERSION" type="Flash Component">
<author name="YOUR NAME"/>
<!-- List the required/compatible products -->
<products>
<product name="Flash" version="FLASH VERSION" required="true"/>
</products>
<!-- Describe the extension -->
<description>
<![CDATA[
DESCRIBE YOUR COMPONENT HERE. THIS WILL APPEAR IN THE EXTENSION MANAGER.
]]>
</description>
<!-- Describe where the extension shows in the UI of the product -->
<ui-access>
<![CDATA[
This item can be found in the components panel.
]]>
</ui-access>
<!-- Describe the files that comprise the extension. Make sure that you save
The .mxi in the same directory as the .swc file(s)
-->
<files>
<file name="NAME_OF_SWC.swc" destination="$flash/Components/CUSTOM DIRECTORY NAME"/>
</files>
</macromedia-extension>

Notice that the .mxi file is looking for a .swc file. A .swc is actually a Flash component file. One of the last things we still need to do is go back to the .fla file, right click on your new component again and choose "Export SWC File". Save the .swc to your working directory (where your icon.png, Slider.fla, and Slider.as all reside) as "Slider.swc". Now customize the .mxi file as you see fit. Mine looks like this:

Code:
<macromedia-extension name="OBO Slider" version="1.0" type="Flash Component">
<!-- Describe the author -->
<author name="Devon O. Wolgang"/>
<!-- List the required/compatible products -->
<products>
<product name="Flash" version="7" required="true"/>
</products>
<!-- Describe the extension -->
<description><![CDATA[
Drag the component from the components panel to the stage. This component features the methods:
getValue()
setInitValue()
and
setMaxValue()
]]>
</description>
<!-- Describe where the extension shows in the UI of the product -->
<ui-access><![CDATA[
This item can be found in the components panel.
]]></ui-access>
<!-- Describe the files that comprise the extension. Make sure that you save
The .mxi in the same directory as the .swc file(s)
-->
<files>
<file name="Slider.swc" destination="$flash/Components/One by One Slider"/>
</files>
</macromedia-extension>

Finally, (we're in the home stretch, now), open up the Macromedia Extension Manager and from the File menu, choose "Package Extension". Browse to your .mxi file, select it and click okay. And there you go: a shiny new .mxp file of your slider component that you can upload to the Macromedia Exchange, sell on Ebay, or share with all your friends (telling them how incredibly difficult it was to make).

So, to recap the component construction process, create a movie clip, link the movie clip to a class file, add some component metadata to your class, define the movie clip as a component, export the .swc file, and package the .swc using a .mxi file and Extension Manager. Easy Peasy..

The final .mxp I created can be downloaded here.

Enjoy...
Comments Feed Comments Feed: http://www.liuhuan.com/blog/feed.asp?q=comment&id=492

There is no comment on this article.

You can't post comment on this article.