Display List: Chapter 6 - ActionScript 3.0 Cookbook

by Joey Lott, Darron Schall, Keith Peters

This excerpt is from ActionScript 3.0 Cookbook. Well before Ajax and Windows Presentation Foundation, Macromedia Flash provided the first method for building "rich" web pages. Now, Adobe is making Flash a full-fledged development environment, and learning ActionScript 3.0 is key. That's a challenge for even the most experienced Flash developer. This Cookbook offers more than 300 solutions to solve a wide range of coding dilemmas, so you can learn to work with the new version right away.

buy button

Section 6.0: Introduction

The rendering model for ActionScript 3.0 and Flash Player 9 is radically different than in previous versions. Traditionally, the MovieClip was the focal point of the renderer. Every .swf movie contained a root MovieClip (commonly referred to as the Stage). The root MovieClip could contain child MovieClips, which could, in turn, contain more child MovieClips. The concept of depths was used to control the stacking order in which MovieClips were drawn (objects on higher depths appear "on top"). Methods such as createEmptyMovieClip( ), attachMovie( ), or duplicateMovie- Clip( ) were used to create MovieClips. Anytime a MovieClip was created, it was automatically added into the visual hierarchy and consequently drawn by the renderer. MovieClips weren't able to move to different places within the hierarchy; instead, they first had to be destroyed and then recreated before they could be positioned elsewhere in the display.

The new renderer is still hierarchical, but not as rigid, and aims to simplify and optimize the rendering process. The new rendering model centers on the display list concept and focuses on the classes available in the flash.display package. The display list is a hierarchy that contains all visible objects in the .swf movie. Any object not on the display list is not drawn by the renderer. Each .swf movie contains exactly one display list, which is comprised of three types of elements:

The stage
The stage is the root of the display list hierarchy. Every movie has a single stage object that contains the entire object hierarchy of everything displaying on the screen. The stage is a container that typically contains only a single child, the main application class of the .swf movie. You can access the stage by referring to the stage property on any display object in the display list.
Display object containers
A display object container is an object that is capable of containing child display objects. The stage is a display object container. Other display object containers include Sprite, MovieClip, and Shape. When a display object container is removed from the display list, all its children are removed as well.
Display objects
A display object is a visual element. Some classes function as both display objects and display object containers, such as MovieClip, while other classes are only display objects, such a TextField. After a display object is created, it won't appear on-screen until it is added into a display object container.

The hierarchy tree for a display list might look like something in Figure 6-1. The stage is at the very top of the hierarchy, with display object containers as branches and display objects as leaves. The items at the top of the diagram are visually underneath the items at the bottom.

Example
Figure 6-1: An example display list hierarchy

Transitioning to the display list realizes a number of benefits for programmers over working with previous versions of the Flash Player; these include:

Increased performance
The display list contains multiple visual classes besides just MovieClip. Classes such as Sprite can be used to reduce the memory requirements when a timeline isn't necessary. Additionally, a Shape can be used to draw into rather than relying on a full MovieClip instance. By having these lighter-weight classes and using them when possible, precious memory and processor resources can be saved, resulting in improved overall performance.
Easier depth management
The hierarchy of the display functions as depth management under the new display list model. In the previous model, methods such as getNextHighestDepth( ) were used to create MovieClips on the correct depth, and swapDepths( ) was used to control the visual stacking order. Depth management was cumbersome and tedious before and often required extremely careful programming. It was a fact of life that just had to be dealt with because it was so intertwined with the language. The new display list model handles depth almost automatically now, making depth management almost a thing of the past.
Less rigid structure
The previous model featured a fairly inflexible and rigid hierarchy. To change the hierarchy, MovieClips had to be destroyed and recreated at a new location. This was a time-consuming and expensive operation, and often was painfully slow. The new display list model is much more flexible--entire portions of the hierarchy tree can be moved via the new reparenting functionality (discussed in Recipe 6.1), without suffering the performance penalties of creating and destroying elements as before.
Easier creation of visual items
The display list rendering model makes creating display objects easier, especially when creating instances of custom visual classes. The previous model required extending MovieClip, combined with using special linkage that associated a library item with the ActionScript class. Then attachMovie( ) had to be used to actually create an instance of the custom class. Under the display list model, you extend one of the many display object classes, but you use the new keyword to create instances of custom visual classes, which is much more intuitive, easier, and cleaner. See Recipe 6.4 for details.

As noted earlier, the flash.display package contains the core classes for the display list model. The old model focused on the MovieClip class, but the display list revolves around the DisplayObject class and its various subclasses. Figure 6-2 illustrates the display list class hierarchy.

Example
Figure 6-2: The display list class hierarchy

Each one of the core classes is designed to serve a specific purpose. By having more than just MovieClip available, the display list offers more flexibility to programmers than the previous model. The more commonly used display classes are listed in Table 6-1.

Table 6-1: Commonly used display classes
Display class Description
DisplayObject The base class of all display list classes. DisplayObject defines properties and methods common to all display classes. The DisplayObject class is not meant to be instantiated directly.
Bitmap The Bitmap class allows for the creation and manipulation of images via the BitmapData methods; it is described in Chapter 8.
Shape The Shape class contains a graphics property that allows for drawing using lines, fills, circles, rectangles, etc. Chapter 7 has more information.
Sprite Sprites are similar to shapes, but can contain child display objects such as text and video. A Sprite can be thought of as a MovieClip without a timeline.
MovieClip MovieClip is the familiar class with a timeline and methods for controlling the playhead. Because MovieClip is a subclass of Sprite, you can draw inside of it, and it can contain child display objects as well.
Video The Video class lives in the flash.media package, but is also a subclass of DisplayObject. Video instances are used to play video, as described in Chapter 16.
TextField The TextField class, found in the flash.text package, allows the creation of dynamic and input text fields. See Chapter 9 for more information.
Loader Loader instances are used to load in external visual assets, such as other .swf movies or image files.

If you've worked with Flash in the past, transitioning to the display list model is going to take some time. Old habits are hard to break, and display list programming has a lot of depth. However, once you get into the swing of things you'll see that the time it takes to learn the new model is well worth it. The display list dramatically changes Flash display programming for the better.

Section 6.1: Adding an Item to the Display List

Problem

You want to add a new display object to the display list so it appears on-screen.

Solution

Use the addChild( ) and addChildAt( ) methods from the DisplayObectContainer class.

Discussion

The Flash Player is composed of two main pieces that function together to form a cohesive unit, the ActionScript Virtual Machine (AVM) and the Rendering Engine. The AVM is responsible for executing ActionScript code, and the Rendering Engine is what draws objects on-screen. Because the Flash Player is composed of these two main pieces, drawing an object on the screen is a two-step process:

  1. The display object needs to be created in the ActionScript engine.
  2. The display object is then created in the rendering engine and drawn on-screen.

The first step is done by using the new operator to create an instance of the display object. Any object that is going to be added to the display list must be either a direct or indirect subclass of DisplayObject, such as Sprite, MovieClip, TextField, or a custom class you create (according to Recipe 6.4). To create a TextField instance you would use the following code:

var hello:TextField = new TextField( );

The preceding line of code creates a TextField display object in the AVM, but the object is not drawn on the screen yet because the object doesn't exist in the Rendering Engine. To create the object in the Rendering Engine, the object needs to be added to the display list hierarchy. This can be done by calling the addChild( ) or addChildAt( ) method from a DisplayObjectContainer instance that is itself already on the display list hierarchy.

The addChild( ) method takes a single parameter--the display object that the container should add as a child. The following code is a complete example that demonstrates how to create an object in the AVM and then create the object in the Rendering Engine by adding it to the display list:

package {
  import flash.display.DisplayObjectContainer;
  import flash.display.Sprite;
  import flash.text.TextField;

  public class DisplayListExample extends Sprite {
    public function DisplayListExample( ) {
      // Create a display object in the actionscript engine
      var hello:TextField = new TextField( );
      hello.text = "hello";
      
      // Create the display object in the rendering engine
      // by adding it to the display list so that the 
      // text field is drawn on the screen
      addChild( hello );
    }
  }
}

Here the DisplayListExample class is the main application class for the .swf movie and it extends the Sprite class. Because of the class hierarchy described in Figure 6-2, the DisplayListExample is therefore an indirect subclass of DisplayObjectContainer and is capable of having multiple DisplayObject instances as children. This allows for the use of the addChild( ) method to add a display object as a child in the container.

A TextField display object is created in the DisplayListExample constructor, which creates the object inside the AVM. At this point, the object won't appear on-screen because the Rendering Engine doesn't know about it yet. It is only after the object is added to the display list--via the addChild( ) method call--that the TextField is displayed.

The addChild( ) and addChildAt( ) methods only add display objects as children of display object containers. They do not necessarily add display objects to the display list. Children are added to the display list only if the container they are being added to is on the display list as well.

The following code snippet demonstrates how the addChild( ) method doesn't guarantee that a display object is added to the display list. A container is created with some text inside of it, but because the container is not on the display list, the text is not visible:

// Create a text field to display some text
var hello:TextField = new TextField( );
hello.text = "hello";

// Create a container to hold the TextField
var container:Sprite = new Sprite( );
// Add the TextField as a child of the container
container.addChild( hello );

To make the text display on-screen, the text container needs to be added to the display list. This is accomplished by referencing a display object container already on the display list, such as root or stage, calling addChild( ), and passing in the text container display object. Both root and stage are properties of the DisplayObject class:

// Cast the special root reference as a container and add the 
// container that holds the text so it appears on-screen
DisplayObjectContainer( root ).addChild( container );

Display object containers are capable of holding multiple children. The container keeps a list of children internally, and the order of the children in the list determines the visual stacking order on-screen. Each child has a specific position in the list as specified by an integer index value, much like an array. Position 0 is the very bottom of the list and is drawn underneath the child at position 1, which is, in turn, drawn underneath the child at position 2, etc. This is similar to the depth concept you may be familiar with if you have prior Flash experience, but it's easier to manage. There are no gaps between position numbers. That is, there can never be children at position 0 and position 2 with an opening at position 1.

When a new child display object is added via the addChild( ) method, it is drawn visually on top of all of the other children in the container because addChild( ) places the child at the front of the children list, giving it the next highest position index. To add a child and specify where it belongs in the visual stacking order at the same time, use the addChildAt( ) method.

The addChildAt( ) method takes two parameters: the child display object to add, and the position in the stacking order that the child should use. Specifying a position of 0 causes the child to be added to the very bottom of the list and makes the child appear (visually) underneath all of the other children. If there was previously a child at the position specified, all of the children at and above the position index are shifted forward by one to allow the child to be inserted. Specifying an invalid position value, such as a negative value or a number greater than the number of children in the container, generates a RangeError and causes the child to not be added.

The following example creates three different colored circles. The red and blue circles are added with the addChild( ) method, making the blue circle appear on top because it was added after the red circle. After the two calls to addChild( ), the red circle is at position 0 and the blue circle is at position 1. The green circle is then inserted between the two with the addChildAt( ) method, specifying position 1 as the location in the list. The blue circle, previously at position 1, is shifted to position 2 and the green circle is inserted at position 1 in its place. The final result is the red circle at position 0 being drawn underneath the green circle at position 1, and the green circle being drawn underneath blue circle at position 2.

package {
  import flash.display.*;
  public class CircleExample extends Sprite {
    public function CircleExample( ) {
      // Create three different colored circles and
      // change their coordinates so they are staggered
      // and aren't all located at (0,0).
      var red:Shape = createCircle( 0xFF0000, 10 );
      red.x = 10;
      red.y = 20;
      var green:Shape = createCircle( 0x00FF00, 10 );
      green.x = 15;
      green.y = 25;
      var blue:Shape = createCircle( 0x0000FF, 10 );
      blue.x = 20;
      blue.y = 20;
      
      // First add the red circle, then add the blue circle (so blue 
      // is drawn on top of red)
      addChild( red );
      addChild( blue );
      
      // Place the green circle between the red and blue circles
      addChildAt( green, 1 );
    }
    
    // Helper function to create a circle shape with a given color
    // and radius
    public function createCircle( color:uint, radius:Number ):Shape {
      var shape:Shape = new Shape( );
      shape.graphics.beginFill( color );
      shape.graphics.drawCircle( 0, 0, radius );
      shape.graphics.endFill( );
      return shape;
    }
  }
}

So far we've only talked about adding new items to the display list, but what happens when addChild( ) is used on a child that is already on the display list, as a child of another container? This is the concept of reparenting. The child is removed from the container that it currently resides in and is placed in the container that it is being added to.

When you reparent a display object, it is not necessary to remove it first. The addChild( ) method takes care of that for you.

The following example shows reparenting in action. A container is created to display red, green, and blue circles that are all added as children, and the container is added to the display list. Another container is created and added to the display list as well, and then the red circle is moved from the first container to the second. Because the second container is visually above the first container in the display list, all children in the second container appear on top of the children in the first container. This makes the red circle display ontop of the blue and green ones. The red circle was reparented from the first container to the second simply by calling the addChild( ) method.

package {
  import flash.display.*;
  public class DisplayListExample extends Sprite {
    public function DisplayListExample( ) {
      // Create three different colored circles and
      // change their coordinates so they are staggered
      // and aren't all located at (0,0).
      var red:Shape = createCircle( 0xFF0000, 10 );
      red.x = 10;
      red.y = 20;
      var green:Shape = createCircle( 0x00FF00, 10 );
      green.x = 15;
      green.y = 25;
      var blue:Shape = createCircle( 0x0000FF, 10 );
      blue.x = 20;
      blue.y = 20;
      
      // Create a container to hold the three circles, and add the
      // circles to the container
      var container1:Sprite = new Sprite( );
      container1.addChild( red );
      container1.addChild( green );
      container1.addChild( blue );
      
      // Add the container to the display list
      addChild( container1 );
      
      // Create a second container and add it the display list
      var container2:Sprite = new Sprite( );
      addChild( container2 );
      
      // Reparent the red circle from container 1 to container 2,
      // which has the net effect of the red circle being drawn
      // on top of the green and blue ones.
      container2.addChild( red );
    }
    
    // Helper function to create a circle shape with a given color
    // and radius
    public function createCircle( color:uint, radius:Number ):Shape {
      var shape:Shape = new Shape( );
      shape.graphics.beginFill( color );
      shape.graphics.drawCircle( 0, 0, radius );
      shape.graphics.endFill( );
      return shape;
    }
  }
}

See Also

Recipes 6.2 and 6.4

Section 6.2: Removing an Item from the Display List

Problem

You want to remove an item from the display list and consequently remove it from the screen.

Solution

Use the removeChild( ) and removeChildAt( ) methods from the DisplayObectContainer class.

Discussion

Recipe 6.1 demonstrates how to add display objects to the display list using the addChild( ) and addChildAt( ) methods. To achieve the opposite effect and remove a child via one of these methods, use either the removeChild( ) or removeChildAt( ) method.

The removeChild( ) method takes a single parameter, which is a reference to the display object that should be removed from the container. If an object is supposed to be removed and it isn't a child of the container, an ArgumentError is thrown:

package {
  import flash.display.Sprite;
  import flash.text.TextField;
  import flash.events.MouseEvent;

  public class RemoveChildExample extends Sprite {
    
    // Create a local variable to store a reference
    // to the TextField so that we can remove it later
    private var _label:TextField;
    
    public function RemoveChildExample( ) {
      _label = new TextField( );
      _label.text = "Some Text";
      
      // Add the hello TextField to the display list
      addChild( _label );
      
      // When the mouse is clicked anywhere on the stage,
      // remove the label
      stage.addEventListener( MouseEvent.CLICK, removeLabel );
    }
    
    // Removes the label from this container's display list
    public function removeLabel( event:MouseEvent ):void {
      removeChild( _label );
    }
  }
}

The preceding code example creates a local variable label that stores a reference to the TextField within the class itself. This is a necessary step because the removeChild( ) method must be passed a reference to the display object to remove, so label is used to store the reference for later. If label were not available, extra work would be required to get a reference to the TextField to remove it, or the removeChildAt( ) method could be used instead.

In the case whenyou do not have a reference to the display object you want to remove, you can use the removeChildAt( ) method. Similar to the addChildAt( ) method, the removeChildAt( ) method takes a single parameter--the index in the container's list of child display objects to remove. The possible values for the index can range from 0 to numChildren -1. If an invalid index is specified, such as a negative value or a value greater than the number of children in the container, a RangeError is thrown and no child7 is removed. Adopting the previous code snippet to use removeChildAt( ) instead yields the following:

package {
  import flash.display.Sprite;
  import flash.text.TextField;
  import flash.events.MouseEvent;

  public class DisplayListExample extends Sprite {
    
    public function DisplayListExample( ) {
      var label:TextField = new TextField( );
      label.text = "Some Text";
      
      // Add the hello TextField to the display list
      addChild( label );
      
      // When the mouse is clicked anywhere on the stage,
      // remove the label
      stage.addEventListener( MouseEvent.CLICK, removeLabel );
    }
    
    // Removes the label from this container's display list
    public function removeLabel( event:MouseEvent ):void {
      // Only remove the label if it exists
      if ( numChildren > 0 ) {
        removeChildAt( 0 );
      }
    }
  }
}

The biggest change by switching to removeChildAt( ) is that you no longer need to declare label to store a reference to the TextField so it can be removed later. Instead, when label is added to the RemoveChildExample container, it is added at position 0. To remove the label display object, simply remove the child at position 0.

Removing a child display object does not delete it entirely. Instead, it just removes it from the container and prevents the object from being drawn. To completely delete the display object, set all references to the object to null.

If you want to remove all of a container's children, combine removeChildAt( ) with a for loop. Every container has a numChildren property that indicates how many display objects are in the container's display list. By looping one time for each child, you can remove each child based on its position in the container's child list. This is somewhat of a tricky process because of how the position value works.

Whenever a child is removed from a certain position, all of the other children with higher positions shift their position values down by one to eliminate the gap. For example, consider a container with three children at positions 0, 1, and 2. When the child at position 0 is removed, the child at position 1 shifts down to position 0 and the child at position 2 shifts down to position 1. Because the position values change every time a child is removed, there are two ways to handle the removal of all children:

  1. Always remove the child at position 0.
  2. Remove the children backward, starting from the end.

In the first case, because there will always be a child at position 0 as long as the display object has children, you can continue to remove what is at position 0 because it is a new child display object during each loop iteration.

In the second case, removing the very last child from the container does not cause any children to adjust their positions. Only children with a higher position value than what is removed are shifted down by one. The last child has the highest position value within the container; therefore, no other children need to have their positions adjusted.

The first approach is the one we recommend using, and has been implemented in the ascb.util.DisplayObjectUtilities class:

package ascb.util {
  import flash.display.*;
  public class DisplayObjectUtilities {
    // Remove all of the children in a container
    public static function removeAllChildren( 
                             container:DisplayObjectContainer ):void {
      
      // Because the numChildren value changes after every time 
      // you remove a child, save the original value so you can 
      // count correctly
      var count:int = container.numChildren;
      
      // Loop over the children in the container and remove them
      for ( var i:int = 0; i < count; i++ ) {
        container.removeChildAt( 0 );
      }
    }
  }
}

Using the DisplayObjectUtilities.removeAllChildren( ) method is relatively straight- forward, as shown here:

package {
  import flash.display.*;
  import ascb.util.DisplayObjectUtilities;  

  public class DisplayListExample extends Sprite {
    
    public function DisplayListExample( ) {
      
      // Add some empty sprites
      addChild( new Sprite( ) );
      addChild( new Sprite( ) );
      
      // Remove all children from this container
      DisplayObjectUtilities.removeAllChildren( this );
      
      // Demonstrate that all children have been removed - displays: 0
      trace( numChildren );
    }
   
  }
}

See Also

Recipe 6.1

Pages: 1, 2, 3, 4

Next Pagearrow