During Part 1 of this lengthy tutorial we covered several useful programming techniques including planning, preparing and loading XML, variable and function declaration, scaling, alignment and plenty of others. Let’s now finish off our deceptively simple (and very flexible) copyright notice..
Step 25: TextFormat Object Properties
If you recall from Part 1, all our text-formatting variables now hold the relevant values. We can assign those variables to the properties of the TextFormat object we created even earlier. This time let’s skip the generic model and just do it:
//ASSIGN THE VARIABLES TO THE PROPERTIES OF THE TEXT FORMAT //font tfCopyright.font=theFont; //size tfCopyright.size=theFontSize; //color tfCopyright.color=theFontColor; //url tfCopyright.url=theLink; //target tfCopyright.target=theTarget; //bold tfCopyright.bold=theFontBold; //italic tfCopyright.italic=theFontItalic; //underline tfCopyright.underline=theFontUnderline;
Step 26: Auto-Size the TextField
Since our TextField displays text dynamically, and the text can theoretically be of any length, we should auto-size the text field. To do that, we’ll also have to align the text left or right. I say left:
//AUTO-SIZE THE TEXT FIELD AND LEFT-ALIGN THE TEXT IN IT txtCopyright.autoSize=TextFieldAutoSize.LEFT;
Step 27: Apply the TextFormat Object
All our fine text formatting work would be for nothing if we didn’t apply it to the actual TextField. Now would be the good time to do that:
//APPLY TEXT FORMAT TO THE TEXT FIELD txtCopyright.setTextFormat(tfCopyright);
As you can se, we are using the setTextFormat() method of the TextField class, passing the name of our TextFormat variable as a single argument.
We’ve just completed the text formatting part of our code. Here’s what our main function should look by now:
//THE MAIN FUNCTION function makeCopyright(copyright:XML):void { //Get the initial year from XML initialYear=copyright.initialYear.text(); //Get the copyright holder text from XML theHolder=copyright.theHolder.text(); //Get the statement text from XML theStatement=copyright.theStatement.text(); //Get the current year from the local system currentDate = new Date(); currentYear=currentDate.getFullYear(); currentYearString=currentYear.toString(); //Create the text field object txtCopyright = new TextField(); //Add the TextField object to the Display List addChild(txtCopyright); //or stage.addChild //Display text in the TextField txtCopyright.text="COPYRIGHT © "+initialYear+"–"+currentYearString+" "+theHolder+" "+theStatement; //FORMAT THE TEXT //Create TextFormat object tfCopyright = new TextFormat(); //GET THE STRING AND NUMERIC VALUES FOR TEXT FORMAT FROM XML //theFont theFont=copyright.theFont.text(); //theFontSize theFontSize=copyright.theFontSize.text(); //theFontColor theFontColor=copyright.theFontColor.text(); //theLink theLink=copyright.theLink.text(); //theTarget theTarget=copyright.theTarget.text(); //CONVERT STRINGS TO BOOLEANS AND HANDLE POSSIBLE ERRORS //theFontBold theFontBoldString=copyright.theFontBold.text(); if (theFontBoldString=="true") { theFontBold=true; } else if (theFontBoldString == "false") { theFontBold=false; } else { //Handle the error txtCopyright.text = "Please set the correct Boolean value in theFontBold XML item."; } //theFontItalic theFontItalicString=copyright.theFontItalic.text(); if (theFontItalicString=="true") { theFontItalic=true; } else if (theFontItalicString == "false") { theFontItalic=false; } else { //Handle the error txtCopyright.text = "Please set the correct Boolean value in theFontItalic XML item."; } //theFontUnderline theFontUnderlineString=copyright.theFontUnderline.text(); if (theFontUnderlineString=="true") { theFontUnderline=true; } else if (theFontUnderlineString == "false") { theFontUnderline=false; } else { //Handle the error txtCopyright.text = "Please set the correct Boolean value in theFontUnderline XML item."; } //ASSIGN THE VARIABLES TO THE PROPERTIES OF THE TEXT FORMAT //font tfCopyright.font = theFont; //size tfCopyright.size=theFontSize; //color tfCopyright.color=theFontColor; //url tfCopyright.url=theLink; //target tfCopyright.target=theTarget; //bold tfCopyright.bold=theFontBold; //italic tfCopyright.italic=theFontItalic; //underline tfCopyright.underline=theFontUnderline; //AUTO-SIZE THE TEXT FIELD AND LEFT-ALIGN THE TEXT IN IT txtCopyright.autoSize=TextFieldAutoSize.LEFT; //APPLY TEXT FORMAT TO THE TEXT FIELD txtCopyright.setTextFormat(tfCopyright); } // Closes the main function
And this is what we should see after we resave the .as and republish the .swf:
All the text formatting values have been passed from XML correctly, and the look of our copyright notice can now be modified from XML. I encourage you to experiment by changing the text formatting values in the Copyright.xml file. You will also notice that our copyright notice is now a link that opens the new page in the new browser window.
Step 28: Position and Opacity Values
The second major part of code in our main function will position our copyright notice at the place set in the XML file, with or without animation. To make that happen, we need to get the rest of the values from XML. The way it’s done is already familiar from the previous steps, so let’s just write the code. Don’t know about you but I’m having a major deja vu right now:
//GET THE VALUES FROM XML FOR THE VARIABLES THAT POSITION THE TEXT FIELD AND CONTROL ITS OPACITY //theXOffset theXOffset = copyright.theXOffset.text(); //theYOffset theYOffset = copyright.theYOffset.text(); //relativeTo relativeTo=copyright.relativeTo.text(); //leftOrRight leftOrRight=copyright.leftOrRight.text(); //Convert String to Boolean for slideIn slideInString=copyright.slideIn.text(); if (slideInString=="true") { slideIn=true; } else if (slideInString == "false") { slideIn=false; } //slideInSpeed slideInSpeed = copyright.slideInSpeed.text(); //slideInSpeedIndex slideInSpeedIndex = copyright.slideInSpeedIndex.text(); //Convert String to Boolean for fadeIn fadeInString=copyright.fadeIn.text(); if (fadeInString=="true") { fadeIn=true; } else if (fadeInString == "false") { fadeIn=false; } //fadeInSpeed fadeInSpeed = copyright.fadeInSpeed.text(); //fadeInSpeedIndex fadeInSpeed = copyright.fadeInSpeed.text(); //initialAlpha initialAlpha=copyright.initialAlpha.text(); //finalAlpha finalAlpha=copyright.finalAlpha.text();
Step 29: Convert String to MovieClip Name
We have only one more value to pass from our XML file to ActionScript, and that value is held in the movieClipName item of our XML file. Let’s get the string value first:
//movieClipName name movieClipName=copyright.movieClipName.text();
We need that variable to get its value from XML because we want to be able to position our copyright notice relative either to the stage or any MovieClip we may choose. Presumably, our Flash project will have a number of MovieClips in its root Timeline, and we may want the copyright notice to appear at the bottom of one of those clips. Knowing the instance name of that clip, we want to be able to set it in the XML file so that we can position the copyright notice without needing to republish the .swf file.
Our movieClipName variable already holds the value passed from XML. In our XML file we set the movieClipName arbitrarily to the value of mcContentModule (and you may remember that such is the instance name of the reference MovieClip we placed near the top of the stage in our .fla file). After we receive that value in our ActionScript code, it becomes “mcCopyrightModule”: a String value in double quotes. We need to convert that value from String to a MovieClip instance name (to remove the quotation marks, so to speak), and to select that instance name from the instance names of all the MovieClips we may have on stage in our Flash project.
To select one MovieClip of many that may be located in the root Timeline of a .fla project, we can use the MovieClip conversion and square brackets syntax, like this:
MovieClip(root[movieClipName])
To be able to manipulate the MovieClip instance that was created outside of our class, we’ll need to reference that MovieClip in the class. To do so, we’ll use the variable of the MovieClip data type we’ve declared just for that occasion:
referenceClip = MovieClip(root[movieClipName]);
We’ll now proceed to write statements and functions that will position and animate our copyright notice, but first let’s see what our main function looks like so far:
//THE MAIN FUNCTION function makeCopyright(copyright:XML):void { //Get the initial year from XML initialYear=copyright.initialYear.text(); //Get the copyright holder text from XML theHolder=copyright.theHolder.text(); //Get the statement text from XML theStatement=copyright.theStatement.text(); //Get the current year from the local system currentDate = new Date(); currentYear=currentDate.getFullYear(); currentYearString=currentYear.toString(); //Create the text field object txtCopyright = new TextField(); //Add the TextField object to the Display List addChild(txtCopyright); //Display text in the TextField txtCopyright.text="COPYRIGHT © "+initialYear+"–"+currentYearString+" "+theHolder+" "+theStatement; //FORMAT THE TEXT //Create TextFormat object tfCopyright = new TextFormat(); //GET THE STRING AND NUMERIC VALUES FOR TEXT FORMAT FROM XML //theFont theFont=copyright.theFont.text(); //theFontSize theFontSize=copyright.theFontSize.text(); //theFontColor theFontColor=copyright.theFontColor.text(); //theLink theLink=copyright.theLink.text(); //theTarget theTarget=copyright.theTarget.text(); //CONVERT STRINGS TO BOOLEANS AND HANDLE POSSIBLE ERRORS //theFontBold theFontBoldString=copyright.theFontBold.text(); if (theFontBoldString=="true") { theFontBold=true; } else if (theFontBoldString == "false") { theFontBold=false; } else { //Handle the error txtCopyright.text = "Please set the correct Boolean value in theFontBold XML item."; } //theFontItalic theFontItalicString=copyright.theFontItalic.text(); if (theFontItalicString=="true") { theFontItalic=true; } else if (theFontItalicString == "false") { theFontItalic=false; } else { //Handle the error txtCopyright.text = "Please set the correct Boolean value in theFontItalic XML item."; } //theFontUnderline theFontUnderlineString=copyright.theFontUnderline.text(); if (theFontUnderlineString=="true") { theFontUnderline=true; } else if (theFontUnderlineString == "false") { theFontUnderline=false; } else { //Handle the error txtCopyright.text = "Please set the correct Boolean value in theFontUnderline XML item."; } //ASSIGN THE VARIABLES TO THE PROPERTIES OF THE TEXT FORMAT //font tfCopyright.font = theFont; //size tfCopyright.size=theFontSize; //color tfCopyright.color=theFontColor; //url tfCopyright.url=theLink; //target tfCopyright.target=theTarget; //bold tfCopyright.bold=theFontBold; //italic tfCopyright.italic=theFontItalic; //underline tfCopyright.underline=theFontUnderline; //AUTO-SIZE THE TEXT FIELD AND LEFT-ALIGN THE TEXT IN IT txtCopyright.autoSize=TextFieldAutoSize.LEFT; //APPLY TEXT FORMAT TO THE TEXT FIELD txtCopyright.setTextFormat(tfCopyright); //GET THE VALUES FROM XML FOR THE VARIABLES THAT POSITION THE TEXT FIELD //theXOffset theXOffset = copyright.theXOffset.text(); //theYOffset theYOffset = copyright.theYOffset.text(); //relativeTo relativeTo=copyright.relativeTo.text(); //leftOrRight leftOrRight=copyright.leftOrRight.text(); //Convert String to Boolean for slideIn slideInString=copyright.slideIn.text(); if (slideInString=="true") { slideIn=true; } else if (slideInString == "false") { slideIn=false; } //slideInSpeed slideInSpeed = copyright.slideInSpeed.text(); //slideISpeedIndex slideInSpeedIndex = copyright.slideInSpeedIndex.text(); //Convert String to Boolean for fadeIn fadeInString=copyright.fadeIn.text(); if (fadeInString=="true") { fadeIn=true; } else if (fadeInString == "false") { fadeIn=false; } //fadeInSpeed fadeInSpeed = copyright.fadeInSpeed.text(); //fadeInSpeedIndex fadeInSpeedIndex = copyright.fadeInSpeedIndex.text(); //initialAlpha initialAlpha=copyright.initialAlpha.text(); //finalAlpha finalAlpha=copyright.finalAlpha.text(); //movieClipName name movieClipName=copyright.movieClipName.text(); //Convert the string into a MovieClip name referenceClip=MovieClip(root[movieClipName]); } //Closes the main function
Step 30: Setting Relative Coordinates
We are now going to create a new function within our main function. This new function will be responsible for setting relative X and Y coordinates for our copyright notice when our movie first loads, and for setting those coordinates again if a user resizes the browser window. All we need to do in this step of our tutorial is define the function:
//SET RELATIVE COORDINATES function setRelativeCoordinates():void { }
Step 31: Position the TextField
The possible position of our copyright notice will be determined by three main variables: relativeX, relativeZeroX and relativeY. If the copyright notice is placed near the right edge of the stage or MovieClip, its X coordinate will be based on the value of the relativeX variable. If the copyright notice is placed near the left edge of the stage or a MovieClip, its X coordinate will be based on the value of the relativeZeroX variable. The Y coordinate of the copyright notice will in both cases be based on the value of the relativeY variable.
The actual values of relativeX, relativeZeroX and relativeY variables should, in their own turn, depend on the value of the relativeTo variable which can hold either “stage” or “movieclip” strings.
If the relativeTo variable holds the “stage” string, relativeX should be equal to the width of the stage, relativeZeroX should be equal to 0, and relativeY should be equal to the height of the stage.
If the relativeTo variable holds the “movieclip” string, relativeX should be equal to the sum of the referenceClip’s X coordinate andd the width of the referenceClip, relativeZeroX should be equal to the X coordinate of the referenceClip, and relativeY should be equal to the sum of the Y coordinate of the referenceClip and its height.
That took a lot of ink to be described in plain English, but can be expressed in a very compact bit of code that we place inside the setRelativeCoordinates function:
//POSITION THE TEXT FIELD RELATIVE TO THE STAGE OR A MOVIECLIP if (relativeTo=="stage") { relativeX=stage.stageWidth; relativeZeroX=0; relativeY=stage.stageHeight; } else if (relativeTo=="movieclip") { relativeX=referenceClip.x+referenceClip.width; relativeZeroX=referenceClip.x; relativeY=referenceClip.y+referenceClip.height; }
Step 32: Draw a Shape for a Mask
When we use slide-in animated effect and position our copyright notice relative to a MovieClip, we don’t want our copyright notice to pop suddenly into existence somewhere in the middle of the stage and then slide along the MovieClip to its final X coordinate. That would be a little too crude. Instead, we want our copyright notice to slide in gracefully out of nowhere. We need a mask to make this happen.
In this step, we’ll draw the shape for the mask, and in the next step, we’ll set the mask to reveal our TextField object when necessary.
You may remember that when we declared our mask variable we assigned it to the MovieClip data type. Let’s now stick the new MovieClip into our variable, and place the variable within the second clause of the code that we wrote in the previous step:
//POSITION THE TEXT FIELD RELATIVE TO THE STAGE OR A MOVIECLIP if (relativeTo=="stage") { relativeX=stage.stageWidth; relativeZeroX=0; relativeY=stage.stageHeight; } else if (relativeTo=="movieclip") { relativeX=referenceClip.x+referenceClip.width; relativeZeroX=referenceClip.x; relativeY=referenceClip.y+referenceClip.height; //DRAW THE MASK copyrightMask = new MovieClip(); }
When we position the copyright notice relative to a MovieClip, we want our mask to be as wide as the MovieClip, minus theXOffset value multiplied by two (one offset on the left and one on the right). We want the mask to be of the same height as our TextField. We want to place the mask at theXOffset distance from the left edge of the MovieClip (that would center the mask horizontally) — and at theYOffset distance from the bottom edge of the MovieClip.
Now let’s draw the mask.
//DRAW THE MASK copyrightMask = new MovieClip(); copyrightMask.graphics.beginFill(0xFF0000,0); copyrightMask.graphics.drawRect(0, 0, referenceClip.width-(theXOffset*2), txtCopyright.height); copyrightMask.graphics.endFill(); copyrightMask.x=referenceClip.x+theXOffset; copyrightMask.y = (referenceClip.y+referenceClip.height)-(txtCopyright.height+theYOffset);
Step 33: Reveal the TextField
Let’s set the mask to reveal the TextField if it’s positioned relative to a MovieClip. The shape of our mask is ready, and we just need to assign the mask to the TextField and add the mask to the Display List:
//ASSIGN THE MASK TO THE TEXT FIELD txtCopyright.mask=copyrightMask; //ADD THE MASK TO THE DISPLAY LIST addChild(copyrightMask);
Now let’s take a look at what we have so far in this part of our code:
//POSITION THE TEXT FIELD RELATIVE TO THE STAGE OR A MOVIECLIP if (relativeTo=="stage") { relativeX=stage.stageWidth; relativeZeroX=0; relativeY=stage.stageHeight; } else if (relativeTo=="movieclip") { relativeX=referenceClip.x+referenceClip.width; relativeZeroX=referenceClip.x; relativeY=referenceClip.y+referenceClip.height; //DRAW THE MASK copyrightMask = new MovieClip(); copyrightMask.graphics.beginFill(0xFF0000,0); copyrightMask.graphics.drawRect(0, 0, referenceClip.width-(theXOffset*2), txtCopyright.height); copyrightMask.graphics.endFill(); copyrightMask.x=referenceClip.x+theXOffset; copyrightMask.y = (referenceClip.y+referenceClip.height)-(txtCopyright.height+theYOffset); //ASSIGN THE MASK TO THE TEXT FIELD txtCopyright.mask=copyrightMask; //ADD THE MASK TO THE DISPLAY LIST addChild(copyrightMask); }
Step 34: Set the Relative Y Coordinate
If our copyright notice is positioned relative to the stage, the distance from the bottom edge of the stage to the copyright notice should remain the same, even when the stage is resized. Similarly, if our copyright notice is placed relative to a MovieClip, the distance from the bottom edge of that MovieClip to the copyright notice should also remain the same. We can write a single statement that will take care of the relative Y coordinate for the TextField that displays our copyright text.
If we make Y coordinate of the TextField equal the relativeY, the top of our TextField will be placed even with the bottom of the stage or MovieClip. We need to subtract the height of the TextField from the relativeY. We may also want to subtract the value of theYOffset variable, if we want our copyright notice to appear slightly above the bottom edge of the stage or a MovieClip.
All of this translates into the following line of code:
//SET THE RELATIVE Y COORDINATE FOR THE TEXT FIELD txtCopyright.y=relativeY-(txtCopyright.height+theYOffset);
Step 35: Assign Values to the Coordinates
Our copyright notice should slide into its final position if the slide-in animated effect toggled on in XML, or just appear at that position of the slide-in effect is toggled off. In both cases, the final X coordinate for the copyright notice is the same.
If our copyright notice is set to appear at the right edge of the stage or a MovieClip, we should subtract the width of the copyright notice and the value of theXOffset from the relativeX. If our copyright notice is set to appear at the left edge of the stage or a MovieClip, we should add theXOffset to the relativeZeroX.
Therefore, we need a left final relative coordinate and a right final relative coordinate:
//ASSIGN VALUES TO THE FINAL LEFT AND RIGHT X COORDINATES FOR THE TEXT FIELD xFinalRight = relativeX-(txtCopyright.width+theXOffset); xFinalLeft=relativeZeroX+theXOffset;
Step 36: Final Relative X Coordinate
We need to generalize the final X coordinate for our TextField but placing it into its own variable whose value would change depending on the value of the leftOrRight variable. We do this by the means of the if…else if conditional statement:
//ASSIGN THE VALUE TO THE FINAL RELATIVE X COORDINATE if (leftOrRight=="right") { xFinal=xFinalRight; } else if (leftOrRight == "left") { xFinal=xFinalLeft; }
Step 37: Reposition the TextField on Resize
Assuming that all MovieClips in our main Flash project are of a fixed size, the stage is the only object that will require our copyright notice to update its position if a user resizes the browser window. We’ve already set the relative Y coordinate that will update whenever the stage is resized. If the copyright notice is positioned at the left edge of the stage, resizing the browser window will not affect the relative X coordinate of the TextField. However, if the copyright notice is positioned at the right edge of the stage, the final relative X coordinate of the text field will have to update every time the browser window is resized. The task is very clear and all we need to do is explain it to Flash in its own language:
//REPOSITION THE TEXT FIELD ON RESIZE IF ITS PLACED RELATIVE TO STAGE AT THE RIGHT if (leftOrRight=="right"&&relativeTo=="stage") { txtCopyright.x=xFinalRight; }
This completes the setRelativeCoordinates function, and the whole function should look like this:
//SET RELATIVE COORDINATES function setRelativeCoordinates():void { //POSITION THE TEXT FIELD RELATIVE TO THE STAGE OR A MOVIECLIP if (relativeTo=="stage") { relativeX=stage.stageWidth; relativeZeroX=0; relativeY=stage.stageHeight; } else if (relativeTo=="movieclip") { relativeX=referenceClip.x+referenceClip.width; relativeZeroX=referenceClip.x; relativeY=referenceClip.y+referenceClip.height; //DRAW THE MASK copyrightMask = new MovieClip(); copyrightMask.graphics.beginFill(0xFF0000,1); copyrightMask.graphics.drawRect(0, 0, referenceClip.width-(theXOffset*2), txtCopyright.height); copyrightMask.graphics.endFill(); copyrightMask.x=referenceClip.x+theXOffset; copyrightMask.y = (referenceClip.y+referenceClip.height)-(txtCopyright.height+theYOffset); //ASSIGN THE MASK TO THE TEXT FIELD txtCopyright.mask=copyrightMask; //ADD THE MASK TO THE DISPLAY LIST addChild(copyrightMask); } } //SET THE RELATIVE Y COORDINATE FOR THE TEXT FIELD txtCopyright.y=relativeY-(txtCopyright.height+theYOffset); //ASSIGN VALUES TO THE FINAL LEFT AND RIGHT X COORDINATES FOR THE TEXT FIELD xFinalRight = relativeX-(txtCopyright.width+theXOffset); xFinalLeft=relativeZeroX+theXOffset; //ASSIGN THE VALUE TO THE FINAL RELATIVE X COORDINATE if (leftOrRight=="right") { xFinal=xFinalRight; } else if (leftOrRight == "left") { xFinal=xFinalLeft; } //REPOSITION THE TEXT FIELD ON RESIZE IF IT'S PLACED RELATIVE TO STAGE AT THE RIGHT if (leftOrRight=="right"&&relativeTo=="stage") { txtCopyright.x=xFinalRight; } //Closes the conditional statement } //Closes setRelativeCoordinates
Step 38: Animation Create Timer
Animation in Flash can be frame-based or timer-based. The speed of the frame-based animation depends on two factors: the frame rate set in the main Flash project and the speed of the computer playing the animation. The slower the computer, the more likely is the actual frame rate of the animation to be lower than its declared frame rate. If the computer is busy with other tasks, the animation may lag.
The speed of the timer-based animation is independent from the speed of the computer that runs it. That speed only depends on the unsigned integer value passed as an argument to an object of the Timer class that controls the animation. That integer sets the period between the “ticks” of the Timer object in milliseconds. That’s why if we want our animation to be smooth, it’s better to make it timer-based.
To control the speed of our animated effects, we have numeric variables holding values received from XML: slideInSpeed, slideInSpeedIndex, fadeInSpeed and fadeInSpeedIndex. Changing their values in XML will change the speed of the animated effects. We also have two Timer variables slideInTimer and fadeInTimer. We’ll get to using the slideInSpeedIndex and fadeInSpeedIndex variables and to creating the Timer for the fade-in effect a bit later, but now it’s time to create the Timer for the slide-in animation. To put a Timer object into the slideInTimer variable we just need to call the constructor of the Timer class, and to pass the slideInSpeed variable to it as the argument:
//CREATE THE TIMER FOR SLIDE-IN ANIMATION slideInTimer = new Timer(slideInSpeed);
Step 39: Animation Create Function
First, let’s define the function, as usual:
//SLIDING FUNCTION //slideCopyright function slideCopyright():void { } //Closes slideCopyright
We want some easing for our slide-in effect to make that effect look a little more lifelike, but all we need is a simple ease-out, and that’s why, as we agreed earlier, it would make no sense for us to import one of the huge, option-rich popular tweener classes like Caurina or TweenLite. We’ll just include the ease-out into the code for our slide-in animated effect. Ideally, it would be great to write the code for the slide-in effect in such a way that both the sliding and the easing are done by the same statement. And we happen to have just the thing for that!
What I’m about to say is obvious but easy to forget. Flash doesn’t create motion. It can’t. It wouldn’t be able to move anything even if its digital life depended on it. All Flash can do is position a visual object at new coordinates every time an instance of a certain event object is created. In our case, every time a Timer event is dispatched, Flash can assign new coordinates to the TextField that displays the copyright information received from XML.
“Easing out” means changing the coordinates of a visual object in such a way that with each next dispatched event, that object is placed closer to the final coordinates at decreasing intervals. To say it another way, for example, if the distance from the current X coordinate of our TextField to its final X coordinate is 80 pixels, and that distance gets divided by two whenever the timer ticks, then at the first tick of a timer our TextField object is repositioned 40 pixels closer to its final X coordinate, at the second tick the TextField is repositioned only 20 pixels closer to its final X coordinates, at the third tick it’s repositioned by 10 pixels, at the fourth tick, by 5, at the fifth tick, by 2.5, at the sixth tick, by 1.25 and so on. In terms of illusion of motion, it would appear that the TextField moves towards its final X coordinate, gradually slowing down. Dividing the distance by two would actually create a really fast animation. But the distance doesn’t have to be divided by two, it can be divided by a higher number. The higher the number, the slower the animation.
To translate that into programmatic terms, we’ll need this data:
- The current X coordinate of our Text Field object, updated with each Timer event
- The final X coordinate the Text Field object should arrive to – that value is contained in our xFinal variable
- The distance between the current X coordinate of the TextField and its final X coordinate, also updated with each Timer event – we’ve prepared the xDistance variable to hold that value
- The division index that would chop off a certain percentage of the xDistance every time the Timer event is dispatched – that index is being passed from XML into the slideInSpeedIndex variable
Having the data, we can now write the code. First, let’s tell Flash how to calculate the distance between the current X coordinate of the TextField and the final X coordinate:
xDistance = xFinal-txtCopyright.x;
This makes perfect logical sense because the definition of a distance is the difference between the current position and the reference position: I am currently in New York and the difference between the coordinates of New York and Paris approximately equals the distance from the coffee cup on my desk to someone else’s steaming coffee cup on the table in Le Cafe Constant.
Now for the actual easing:
txtCopyright.x=txtCopyright.x+xDistance/slideInSpeedIndex;
We’re telling Flash to update the current X coordinate of the TextField by adding to it the numeric value obtained by dividing the current distance to the final X by the index contained in the slideInSpeedIndex variable
We’ve just written the code that does the most important bit of math for our ease-out sliding animation, and yet the animation wouldn’t occur. Why? We haven’t yet started the timer and haven’t created the event listener that would listen for events dispatched by the timer and would run our animation function every time an event is dispatched. But we’re not going to start the timer and add the event listener just yet: we’ll start and add them a bit further in our code, and very soon you’ll see why. On the contrary, we’re now going to stop the timer we haven’t yet started and remove the event listener we haven’t yet added!
I know how crazy that sounded just now, but what I described is actually good coding practice. We should make sure to stop the timer and remove the event listener when they are no longer needed (in our case, when the TextField arrives to its destination), so that the timer stops ticking and the event listener stops checking for events that will never reoccur. In that way we free some of the processing capacity of a user’s computer and, in the long run, save precious natural resources. We may forget or lack time to do that later, when we would have to scroll up from way below and find our slide-in function, so why not do it now when we’re writing the function? In general, you may often find yourself stopping timers and removing event listeners before you started/added them. There’s nothing unusual about that, it’s quite common.
So here’s what we apparently need to write:
//Stop the timer and remove the event listener if (txtCopyright.x==xFinal) { slideInTimer.stop(); slideInTimer.removeEventListener(TimerEvent.TIMER, slideRight); }
We just told Flash to stop the timer and to remove its even listener when the TextField object reaches its final coordinate. The code seems perfectly fine, and yet it won’t work. The problem is, our TextField will never arrive to its final coordinate. Due to the nasty Dichotomy Paradox that has full power over Flash, the TextField will just keep repositioning itself by increasingly smaller increments all through infinity, getting imperceptibly ever closer to the final x coordinate whilst never reaching it.
But we can solve the problem easily by rounding the current X position of the TextField object to the nearest integer. When the TextField is near enough to the xFinal for its coordinate to round to the value equal the value of xFinal, the timer will stop and the event will be removed:
//Stop the timer and remove the event listener if (Math.round(txtCopyright.x)==xFinal) { slideInTimer.stop(); slideInTimer.removeEventListener(TimerEvent.TIMER, slideRight); }
If we want to make it just about perfect, we can also force the TextField object to accept the xFinal coordinate:
//Stop the timer and remove the event listener if (Math.round(txtCopyright.x)==xFinal) { txtCopyright.x=xFinal; slideInTimer.stop(); slideInTimer.removeEventListener(TimerEvent.TIMER, slideRight); }
But perfect is not always best, because, depending on the values we’ll be passing from the XML, if we force the TextField into the final position, that may add a little accentuated movement at the very end to our slide-in animation. We may or may not want that, so I would comment out the line of code that does it, and comment it as optional. Other than that, our sliding function is done, and that’s how it looks:
//SLIDING FUNCTION //slideCopyright function slideCopyright():void { xDistance=xFinal-txtCopyright.x; txtCopyright.x=txtCopyright.x+xDistance/slideInSpeedIndex; //Stop the timer and remove event listener if (Math.round(txtCopyright.x)==xFinal) { //txtCopyright.x=xFinal; //optional slideInTimer.stop(); slideInTimer.removeEventListener(TimerEvent.TIMER, slideCopyright); } } //Closes slideCopyright
Step 40: Placement Without Animation
Compared to the major feat of programming we’ve just accomplished, the function that would place our TextField at its final X coordinate if the slideIn item in our XML file is set to false is really modest:
//NON-SLIDING FUNCTION //placeCopyright function placeCopyright():void { txtCopyright.x=xFinal; } //Closes placeCopyright
Step 41: Define the Positioning Function
We’re about to create the function that will take all the code we’ve written so far and make it come to life. Let’s call that function positionTheCopyright and let’s start by defining it:
function positionTheCopyright():void { }
Step 42: Add a Resize Event Listener
If the copyright notice is positioned relative to the stage, whenever the stage is resized, the coordinates of copyright notice will need to be readjusted. We need an event listener to check for the stage being resized, and an event handler to reposition the TextField that holds the copyright text. The good news is, we’ve already created the event handler, and it’s none other than setRelativeCoordinates function we’ve created what seems like ages ago. Now let’s add the event listener:
//Add the resize event listener stage.addEventListener(Event.RESIZE, setRelativeCoordinates);
Step 43: Dispatch a Resize Event
Our code now depends on the resize event, and nothing would happen before a resize event takes place. But what if nobody actually cares to resizes the stage to set things in motion? To make sure that the TextField receives the command to position itself, we’re going to trigger a single resize event from inside our code. The stage won’t actually get resized (or you can think of the stage being resized from its current width and height to to the same exact width and height), but the Flash will receive the instruction to do perform the same operations it would perform if the stage did get resized:
//Fire a resize event stage.dispatchEvent(new Event(Event.RESIZE));
Step 44: Set the Initial X Coordinate
Our TextField will first materialize at its initial coordinate. If the slide-in animated effect is toggled on, the TextField will slide gently from that initial coordinate to the xFinal position. If the slide-in effect is toggled off, the TextField will instantly jump to the xFinalposition. Such a jump won’t be seen because the initial coordinate of the TextField will make it first appear either off-stage (if the relativeTo variable holds the “stage” value) or outside of the mask (if the relativeTo variable holds the “movieclip” value. In any case, the TextField won’t be seen in its initial position:
//Set the initial X coordinate for the TextField if (leftOrRight=="right") { txtCopyright.x=relativeZeroX-txtCopyright.width; } else if (leftOrRight=="left") { txtCopyright.x=relativeX; }
Step 45: Final Coordinate
We should now invoke our two placing functions, slideCopyright and placeCopyright. Which one gets to do its job would depend on the Boolean value held in the slideIn variable. Let’s set the structure first:
/*Slide in the text field, if slideIn in XML set to true or place the text field if slideIn in XML set to false*/ if (slideIn==true) { //Slide the TextField slideCopyright(); } else if (slideIn == false) { //Place the TextField without sliding placeCopyright(); }
Step 46: Start the Timer
Remember how we stopped the timer that we never started and removed an event listener that we never added? It’s time to start that timer and add that event listener. The timer first:
//Start the timer slideInTimer.start(); }
Step 47: Add the Event Listener
Now the event listener
//Add the event listener slideInTimer.addEventListener(TimerEvent.TIMER, slideCopyright);
The event listener that we just added can hear only the Timer events, and every time such event occurs, the event listener invokes the event handler that we (surprise!) have already created. The inventively named slideCopyright function is that event handler.
I am sure you can see now why we actually stopped the timer before starting it and removed the event listener before adding it. It works like this: when the timer starts ticking, the event listener reacts to every tick and each time invokes the slideCopyright function. That function runs as many times as necessary for the TextField to slide close enough to its xFinal coordinate to trigger the code that stops that timer and removes the event listener. I know it feels a bit like solving the Rubik’s cube, but that’s really the best way to do it.
Step 48: Cache the TextField as a Bitmap
This wouldn’t alter the look of the animation but will take a bit of strain off the computer that will play it. When the TextField is cached as a bitmap, Flash won’t have to re-draw it each time its coordinates change.
//CACHE AS BITMAP txtCopyright.cacheAsBitmap=true;
That concludes the part where we write the positionTheCopyright function, and the whole function should look like this:
//POSITION AND REPOSITION THE TEXT FIELD function positionTheCopyright():void { //Add the resize event listener stage.addEventListener(Event.RESIZE, setRelativeCoordinates); //Fire a resize event stage.dispatchEvent(new Event(Event.RESIZE)); //Set the initial X coordinate for the TextField if (leftOrRight=="right") { txtCopyright.x=relativeZeroX-txtCopyright.width; } else if (leftOrRight=="left") { txtCopyright.x=relativeX; } /*Slide in the text field, if slideIn in XML set to true or place the text field if slideIn in XML set to false*/ if (slideIn==true) { //Start the timer slideInTimer.start(); //Add the event listener slideInTimer.addEventListener(TimerEvent.TIMER, slideCopyright); //CACHE AS BITMAP txtCopyright.cacheAsBitmap=true; //Slide the TextField slideCopyright(); } else if (slideIn == false) { //Place the TextField without sliding placeCopyright(); } //Closes the else if clause } //Closes positionTheCopyright
Step 49: Call the Function
We’ve written the function, but we haven’t called it yet. Well, we should:
//CALL THE FUNCTION positionTheCopyright();
Now after we resave the .as file and republish the .swf we can test the movie by changing values in XML. Let’s take a look at some of the possible results. (Click anywhere in the images to replay the animations.)
Relative to the stage, slide in right.
Relative to a MovieClip, slide in right.
Relative to a MovieClip, slide in left.
Now let’s test the resizing function: in the following full-screen example, the leftOrRight value in the XML is set to right, and the relativeTo value in XML is set to stage. Resize the browser window it a few times to see how the copyright notice adjusts its position according to the new the size of the window.
Step 50: Fade-In Animated Effect
The fade-in effect is based mostly on the same techniques as the slide-in effect we programmed: it uses the Timer object to dispatch regular events, the event listener to listen to those events and to call the handler whenever a Timer event is dispatched. One of the differences is that since in this case we are not nesting an event listener within another event listener that listens for a different type of event (as we did before), we can write the event handler after the event listener. We can therefore stop the timer after we started it and remove the event listener after we added it… and that’s a big relief.
Here’s the whole event listener/event handler combo:
//Fade in the text field, if fadeIn in XML set to true if (fadeIn==true) { txtCopyright.alpha=initialAlpha; //Create and start the fade-in timer fadeInTimer=new Timer(fadeInSpeed); fadeInTimer.start(); fadeInTimer.addEventListener(TimerEvent.TIMER, fadeInText); //Fade in the text field function fadeInText(event:Event):void { txtCopyright.alpha+=fadeInSpeedIndex; //remove event listener after fade if (txtCopyright.alpha>=finalAlpha) { txtCopyright.alpha=finalAlpha; //optional fadeInTimer.stop(); fadeInTimer.removeEventListener(TimerEvent.TIMER, fadeInText); }//closes if clause }//closes the fadeInText function } else if (fadeIn == false) { txtCopyright.alpha=finalAlpha; }// closes the else if clause
I’m sure you can figure out easily what happens in that code, but I’d like to point out one more thing. The approximate nature of Flash equations is giving us a bit of trouble again when we need to stop the timer and remove the event listener in this function. The opacity of the TextField may never actually equal the finalAlpha, but in this case we cannot round the current value to the nearest integer simply because alpha in ActionScript 3.0 is set in decimals. We could of course multiply the value by a hundred and manipulate the result of multiplication, but we’ll use a more elegant solution instead.
By telling Flash to add the fadeInSpeedIndex value to the current alpha at every tick of the timer, we’re effectively instructing Flash to overshoot the finalAlpha value at some point, and as soon as it happens, the timer is ordered to stop and event listener is removed. So we can get the timer to stop and the event listener to self-remove by using the >= operator instead of the == operator. This bit of code takes care of that:
if (txtCopyright.alpha>=finalAlpha) { txtCopyright.alpha=finalAlpha; //optional fadeInTimer.stop(); fadeInTimer.removeEventListener(TimerEvent.TIMER, fadeInText); }
Let’s test the fade-in effect now by making the appropriate modifications to the XML file, saving all the files and republishing the .swf. (Click anywhere in the image to replay the animations.)
Relative to the stage, static left, fade in
Now let’s hold our breath for a moment and admire the result of our work. Take a look back at your AS file. It’s huge!
Conclusion
There’s nothing spectacular and nothing particularly advanced about the copyright notice class that we’ve created, but in creating it, we covered a lot of ground and became very familiar with a collection of diverse programming techniques. I hope that you find some of them useful in your everyday work. I encourage you to play with the values in the XML file a little more and see for yourself how everything works. You can also test the stopping of the timers and the removal of event handlers by calling the trace() global function right above the statements that tell Flash to remove listeners and stop timers, for example like this:
//Stop the timer and remove event listener if (Math.round(txtCopyright.x)==xFinal) { //txtCopyright.x=xFinal; //optional trace("The timer is stopped and the event listener is removed."); slideInTimer.stop(); slideInTimer.removeEventListener(TimerEvent.TIMER, slideCopyright); }
If you test the code in that way, you’ll see that all our event handlers get removed when the animations finish playing and our timers stop ticking when they are told to.
If for any reason you need to use the material described in this tutorial as the basis for a class implemented as a non-document class, you will have to make a few changes to the code in the ActionScript file. These changes are beyond the scope of this tutorial, but you will find a few additional lines of code in the downloadable version of our Copyright class. The additional lines are commented out, and you will need to uncomment them in order to make the class work as a non-document class.
Thank you for reading this tutorial and working through it till the end.