2008/08/22

Yahoo! Developer Network - Flash Developer Center - Articles - Developing with the Display List in ActionScript 3

from:Yahoo! Developer Network - Flash Developer Center - Articles - Developing with the Display List in ActionScript 3

Introduction to the Display List


With Flash Player 9 and ActionScript 3, Adobe created an easy and powerful way to add, remove, and change MovieClips on the screen. It's called the display list. No longer do you need to call strange functions like createEmptyMovieClip() and attachMovie(). Now, like you do with any other data type in ActionScript, you can call new MovieClip() to create a MovieClip object. To display it on screen, pass the MovieClip to the addChild() method of the desired parent.



If you try out the code above, not much will happen. To create something visible, let's draw on the MovieClip we createed. The graphics property on MovieClips, Sprites, and many other DisplayObjects works a lot like the drawing API in AS2. You can set the line and fill styles and draw lines and curves. To make drawing easier, Adobe also added functions like drawRect() and others for drawing simple shapes.



If you've drawn your own graphics in a library symbol, attaching it to the display list is just as easy. First, make sure to go into the symbol's Properties dialog and check Export for ActionScript. Next, just like with MovieClip, you'll be able to create an instance in a new variable and use it like any other object. For the following example, assume that we've created a symbol called MyLibrarySymbol in the library, and we've exported it for ActionScript. After we add it to the display list, we move it slightly so that it doesn't cover the first MovieClip we added.



Depth management has been simplified in ActionScript 3 too. You may need to realign your understanding of it, though. Before, in Flash 8 and below, you could add MovieClips to any depth you desired. If you liked the number 10000, you could add an item at that depth. If you added another item at depth 10000, you'd run into trouble because both objects would be stored at that depth and they wouldn't display correctly. With the new display list, you're now working with something that's a lot like a stack of books or magazines. You can put a new magazine directly on the top of the stack, or can you raise part of the stack up somewhere in the middle and place the new magazine there. However, unless you have 9999 magazines or more, you cannot put a new magazine at position 10000 because it would be defying the laws of gravity! The display list works the same way in that regard. If you have only three children, and you try to add another child at position 10000, you will receive a runtime error.

When we added the item2 to the display list, it was placed at the top of the stack, or directly above item1.

Below, we create another MyLibrarySymbol and add it to the bottom of the display list at index 0, under item1, using addChildAt(). The first two items will move up to index 1 and 2, respectively. Again, we offset the x and y values slightly so that the new item doesn't completely cover our first two items.



In AS2, you could move your MovieClips up or down depths using the swapDepths() function. This was a little limiting because you might need to move many MovieClips to get every one at the correct new depth. In AS3, we now have several useful functions to do the same thing. The functions swapChildren() and swapChildrenAt() work much like you'd expect. The first function takes two different children of a containing display object and swaps them, while the second takes two indices and automatically determines the children to swap at those positions. Another function, setChildIndex(), gives you a bit more power. You can now move a specific child to a specific index, and the other children will be moved logically out of the way.

In the next example, we move item3 above item1 on the display list. item1 is automatically moved below item3 and back to index 0. Of course, item2 is still at the top of the stack, and it doesn't change position.



Once you no longer need a particular MovieClip, you can remove it from the display list too. AS3 provides two functions for this operation. The first, removeChild(), will remove a specific child, and the second, removeChildAt() will remove the child at a specific index. Below, we remove item2 from the display list. If a removed child leaves a gap, the other items above it will move down as needed to fill the empty space. In this case, item2 is at the top of the stack, so the others will not need to move.



Download DisplayList.fla for a closer look. Try commenting out parts of the code to see each step one by one.

Those are the very basics of display list manipulation. It should be enough to get you started, but there may be a few things that could catch you off guard if you're experienced with AS2. Let's look a little deeper.


Changes to root and parent

In ActionScript 2, many developers find it easy to access the root, parents, and grandparents all along the display hierarchy. These developers may be in for a surprise when migrating to AS3. The AS3 compiler is strict, and it's going to throw errors the next time you try to use code that looks something like this:



At Yahoo!, we highly discourage our fellow developers from climbing through the display list through root. Simply put, it's dangerous. It could lead to all sorts of maintenance headaches if you ever need to change your code in the future. If you make a mistake somewhere, you may be plagued with strange bugs that are very hard to track down.

That, of course, doesn't tell you why the compiler throws an error. Though we discourage using root, we understand that developers can still use it. However, accessing children through root requires a little extra effort these days. The best way to explain the error is to ask a question:

What is root?

Obviously, it's the "root" or base of the tree structure that forms the display list, but what does the compiler think of root? According to the AS3 documentation, root is of type DisplayObject. This data type is an important key to understanding the error.

Why is DisplayObject relevant? First, you need to understand the difference between a "dynamic" class and a "sealed" class in ActionScript 3. A dynamic class will allow you to add and delete variables at runtime. The Object type is the most obvious example. You can always add or remove variables on a plain Object.



A sealed class has the opposite behavior. Sealed classes will not allow you to add or remove variables at runtime. This is the default behavior for classes in AS3. In other words, most classes in AS3 are sealed. Sprite is a good example of a sealed class.



In AS2, all classes were dynamic by default. You could add or remove properties on any variable without errors. This is why it was so easy to use _root to find a specific MovieClip.

Hopefully, you'll see where I'm heading with this. We want to reference children of root, but the language defines root as type DisplayObject. DisplayObject is sealed, like most classes in AS3. Thus, when you try root.appContainer, like in the example from earlier, you're accessing a variable named appContainer that doesn't exist on a DisplayObject. The compiler knows this, and it will throw an error.

By default, when you create a new FLA file, the root is a MovieClip. Like Object, MovieClip is one of the rare dynamic classes in AS3, and we can use that to our advantage.

If you must access a child of root, you need to explicitly tell the compiler, "trust me, root is a MovieClip". This is called casting. The following example will fix the error introduced the first time we tried to access root.



Since we know root is a MovieClip by default, we can cast it to the correct type. Now the compiler understands that root is more than a simple DisplayObject.

Download CastingRoot.fla for a closer look at how to use root in AS3. This FLA file contains a basic Flash application that features two of the Astra libraries available on the Yahoo! Flash Developer Center. A set of buttons lets you choose a library of your choice to view a short description. It's not a complex application, but it follows a familiar page structure that you should easily understand. Here's a preview:

To see a live example, please install Adobe Flash Player version 9 or higher.

You may run into problems if you always try to cast root as MovieClip. There's a reason the language defines it as a DisplayObject. If you're using a Document Class, you have the ability to subclass MovieClip, Sprite, UIComponent, or any other type of DisplayObject. Casting as MovieClip is easy for a basic FLA file, but when we use a Document Class, we need to cast root to a more appropriate datatype.

Imagine that our application uses a Document Class called MyApplication. It is a subclass of Sprite. In this situation, trying to cast root as MovieClip will throw a runtime error because a Sprite is not a MovieClip. If we want to reference something on root we should cast root as the Document Class, which we've named MyApplication.



A simple change, but more appropriate than MovieClip in this case. If your project uses a Document Class, you should always cast root using that class instead of MovieClip. This is true even when the Document Class subclasses MovieClip because the AS3 compiler can make your code run faster when it knows about a sealed class.

Much like when you use root, you need to take special care with parent as well. According to the AS3 documentation, the parent property of any DisplayObject is of type DisplayObjectContainer. As you should be starting to understand now, when you need to reference a MovieClip's parent, you may need to cast the parent to the appropriate type so that the compiler understands that you know what you're doing. Once again, DisplayObjectContainer is a sealed class, so it does not automatically get publicly-accessible properties for its children.

We're going to use the same basic example as we used with root. The application is simple enough that we only need to make minor changes to use parent instead of root to navigate the display list. Hopefully, you'll find it easy to understand since you've already encountered most of the code already.

Inside this application, we have an AppContainer symbol, which contains a MenuContainer symbol that holds the buttons and a PageContainer symbol that holds the library descriptions. Inside our MenuContainer, we listen for when the user presses the buttons. Similar to how we used root, we now want to reference parent in our click handler.



As stated above, the parent property in AS3 is defined as an instance of the sealed class DisplayObjectContainer. As a result, the preceding code will throw an error because DisplayObjectContainer doesn't have a property named pageContainer. However, just like we did with root, we can cast parent to tell the compiler, "it's okay, parent is a MovieClip" (or a more appropriate type, if required).



Once again, casting has helped the compiler understand a bit more about the structure you've created on the display list. Remember to cast as the correct type if parent is not a MovieClip. For instance, parent might be a Sprite instead.

Download CastingParent.fla to view the earlier example updated to use parent instead of root.

The preceding examples should help get many AS2 developers up to speed with the new display list in AS3. It may leave some feeling a little frustrated, though. If you're a Flash developer who uses _root and long strings of _parent._parent._parent in your AS2 code often, you're going to find yourself facing the need to cast objects quite a bit with AS3. Some important tips in the following section may help you improve your code, and you shouldn't need to hack the display list in many common situations.

Display List Best Practices

The Yahoo! Flash Platform team works hard to promote best practices when developing with ActionScript 3. Just like we discourage the use of root, we also want to make sure that you're communicating between components and separate parts of your applications effectively and without too many long strings of parent.parent.parent or equally long strings of children, grandchildren, and deeper items on the display list. For the very same reasons that we don't like using root, we've found that hacking through parents and children can lead to maintenance headaches and difficulties finding bugs.

An effective, object-oriented way to limit display list hacking is a method called encapsulation. Encapsulation involves making sure each component or discrete piece of your application knows only what it needs to know. The application I shared above is not properly encapsulated. For example, when the MenuContainer handles a button press, it updates the PageContainer to display the correct description. Ask yourself, "Should the MenuContainer itself need to know anything about the PageContainer and the pages within?"

No. What if we need to use a similar MenuContainer in the future? This new MenuContainer might not need to change pages. Instead, clicking the buttons might open the download pages for the Astra libraries. The MenuContainer functionality has nothing to do with PageContainers at all, so we'd need to recreate MenuContainer with nearly the same functionality. That can lead to a lot of duplicate work. Save yourself time and keep MenuContainer loosely coupled and generic.

To help make MenuContainer and PageContainer communicate without relying directly on each other, we're going to use events in MenuContainer to tell the application that a button has been pressed. The MenuContainer won't care what happens at this point. It simply dispatches an event that says that "Web APIs" or another option was chosen. The listener(s) can take care of all the details.

First, let's create our new event class, MenuContainerEvent. We start by subclassing flash.events.Event. If you've used Flash's built-in events, like KeyboardEvent or MouseEvent, you know that we need to pass an event type to the constructor. Let's create a new String constant so that the type can be specified using MenuContainerEvent.MENU_ITEM_SELECT. Next, we need a way to include the user's selection with the event, so let's add a selectedItem property to our new event and allow this value to be set in the constructor as well. Finally, we should always override the clone() method when we create a new event class. If you don't, you may encounter some strange runtime errors.



This class is saved in a file named MenuContainerEvent.as in the same directory as the FLA file.

Now, in our MenuContainer symbol, let's change the button click event handlers to dispatch our new event rather than accessing the PageContainer directly.



In our AppContainer, we should now listen for the new MenuContainerEvent and do something when we receive it.



Listening to the event only takes one line, and we will soon create a new function to handle the event, but how should we change the pages in the PageContainer? Calling this.pageContainer.page1.visible = true; would be another example of bad encapsulation. We don't need to know the little details of how the PageContainer works internally. Instead, our PageContainer should have a property to helps to hide the details of how it changes pages. Let's create a selectedPage property on PageContainer so that AppContainer can change this value in the MENU_ITEM_SELECT event handler.



Now, with the selectedPage property in place, we can write our event handler in AppContainer.



Download EncapsulationEvents.fla and MenuContainerEvent.as to view the application with encapsulation using events.

You may need to spend a few minutes studying all the details of the updated application. For someone new to the idea of encapsulation, it can be a bit confusing because it requires building an application or website from a different perspective. I've found that it's much easier to create and maintain Flash applications that are designed with encapsulation from the beginning. You may notice that the updated application doesn't include any of the casting I described in previous sections, and the display list manipulations never go deeper than one level. Each individual part is more independent and reusable!

If we wanted, we could probably go a little bit further with best practices and encapsulation. For instance, I would probably create classes for MenuContainer, PageContainer, and AppContainer. I'm not a big fan of timeline coding because it can be easy to forget where you placed all the code. Individual classes to associate with each symbol would help to make it easier to find your code, and you can edit them in your favorite text editor too. Timeline code doesn't have that flexibility.

Are you a display list master now?

The display list in ActionScript 3 differs greatly from the way we used MovieClips in previous versions of Flash. However, it offers many useful ways to help to make our everyday development easier. The biggest challenge to learning to use the new display list is to unlearn the tricky shortcuts and bad practices that were so prevalent in the days of AS2. Things have changed in many cases, and you should take some time to understand these changes and the reasons why Adobe decided to make them.

For more advanced developers, the last section on encapsulation is probably a healthy review. For someone who might not have any formal training in software engineering, it might be a bit hard to grasp immediately. That's okay, and I certainly understand. I remember back when I first started working with Flash. I was still finishing up my degree, and it took me a while to understand how Object-Oriented Programming (OOP) and best practices could make my job easier. If you want to start using best practices in your code, my strongest suggestion is to simply take your time. There's no rush as long as you're sufficiently motivated.

沒有留言: