Display List: Chapter 6 - ActionScript 3.0 Cookbook
Pages: 1, 2, 3, 4

Section 6.3: Moving Objects Forward and Backward

Problem

You want to change the order in which objects are drawn on-screen, moving them either in front of or behind other display objects.

Solution

Use the setChildIndex( ) method of the DisplayObectContainer class to change the position of a particular item. Use the getChildIndex( ) and getChildAt( ) methods to query siblings of the item so the item can be positioned properly relative to them.

Discussion

Recipes 6.1 and 6.2 introduced how the display list model deals with the visual stacking order (depth). Essentially, every DisplayObjectContainer instance has a list of children, and the order of the children in this list determines the order in which child display objects are drawn inside of the container. The children are each given a position index, ranging from 0 to numChildren - 1, much like an array. The child at position 0 is drawn on the bottom, underneath the child at position 1, etc. There are no empty position values in the list; if there are three children, the children will always have index values of 0, 1, and 2 (and not, say, 0, 1, and 6).

The setChildIndex( ) method is provided by DisplayObjectContainer to reorder the children inside the container. It takes two parameters: a reference to the child to be moved and the child's new position in the container. The index position specified must be a valid value. Negative values or values too large will generate a RangeError and the function won't execute properly.

The following example creates three colored circles, with the blue one being drawn on top. The setChildIndex( ) method is used to move the blue circle underneath the two other circles, changing its position from 2 to 0 in the container. The positions of the other children are adjusted accordingly; red is moved to 1 and green is moved to 2:

package {
  import flash.display.*;
  public class SetChildIndexExample extends Sprite {
    public function SetChildIndexExample( ) {
      // 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;
      
      // Add the circles, red has index 0, green 1, and blue 2
      addChild( red );
      addChild( green );
      addChild( blue );
      
      // Move the blue circle underneath the others by placing
      // it at the very bottom of the list, at index 0
      setChildIndex( blue, 0 );
    }
    
    // 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;
    }
  }
}

One of the requirements for setChildIndex( ) is that you know the index value you want to give to a specific child. When you're sending a child to the back, you use 0 as the index. When you want to bring a child to the very front, you specify numChildren - 1 as the index. But what if you want to move a child underneath another child?

For example, suppose you have two circles--one green and one blue--and you don't know their positions ahead of time. You want to move the blue circle behind the green one, but setChildIndex( ) requires an integer value for the new position. There are no setChildAbove or setChildBelow methods, so the solution is to use the getChildIndex( ) method to retrieve the index of a child, and then use that index to change the position of the other child. The getChildIndex( ) method takes a display object as a parameter and returns the index of the display object in the container. If the display object passed in is not a child of the container, an ArgumentError is thrown.

The following example creates two circles--one green and one blue--and uses getChildIndex( ) on the green circle so the blue circle can be moved beneath it. By setting the blue circle to the index that the green circle has, the blue circle takes over the position and the green circle moves to the next higher position because blue had a higher position initially:

package {
  import flash.display.*;
  public class GetChildIndexExample extends Sprite {
    public function GetChildIndexExample( ) {
      // Create two different sized circles
      var green:Shape = createCircle( 0x00FF00, 10 );
      green.x = 25;
      green.y = 25;
      var blue:Shape = createCircle( 0x0000FF, 20 );
      blue.x = 25;
      blue.y = 25;
      
      // Add the circles to this container
      addChild( green );
      addChild( blue );
      
      // Move the blue circle underneath the green circle. First
      // the index of the green circle is retrieved, and then the
      // blue circle is set to that index.
      setChildIndex( blue, getChildIndex( green ) );
    }
    
    // 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;
    }
  }
}

When a child is moved to an index lower than the one it currently has, all children from the target index up to the one just before the child index will have their indexes increased by 1 and the child is assigned to the target index. When a child is moved to a higher index, all children from the one just above the child index up to and including the target index are moved down by 1, and the child is assigned the target index value.

In general, if object a is above object b, the following code to moves a directly below b:

setChildIndex( a, getChildIndex( b ) );

Conversely, if object a is below object b, the preceding code moves a directly above b.

So far, we've always been moving around children that we've had a reference to. For example, the blue variable referenced the display object for the blue circle, and we were able to use this variable to change the index of the blue circle. What happens when you don't have a reference to the object you want to move, and the blue variable doesn't exist? The setChildIndex( ) method requires a reference to the object as its first parameter, so you'll need to get the reference somehow if it isn't available with a regular variable. The solution is to use the getChildAt( ) method.

The getChildAt( ) method takes a single argument, an index in the container's children list, and returns a reference to the display object located at that index. If the specified index isn't a valid index in the list, a RangeError is thrown.

The following example creates several circles of various colors and sizes and places them at various locations on the screen. Every time the mouse is pressed, the child at the very bottom is placed on top of all of the others:

package {
  import flash.display.*;
  import flash.events.*;
  public class GetChildAtExample extends Sprite {
    public function GetChildAtExample( ) {
      // Define a list of colors to use
      var color:Array = [ 0xFF0000, 0x990000, 0x660000, 0x00FF00,
                          0x009900, 0x006600, 0x0000FF, 0x000099,
                          0x000066, 0xCCCCCC ];
      // Create 10 circles and line them up diagonally
      for ( var i:int = 0; i < 10; i++ ) {
        var circle:Shape = createCircle( color[i], 10 );
        circle.x = i;
        circle.y = i + 10; // the + 10 adds padding from the top
        
        addChild( circle );
      }      
      
      stage.addEventListener( MouseEvent.CLICK, updateDisplay );
    }
    
    // Move the circle at the bottom to the very top
    public function updateDisplay( event:MouseEvent ):void {
      // getChildAt(0) returns the display object on the
      // very bottom, which then gets moved to the top
      // by specifying index numChildren - 1 in setChildIndex
      setChildIndex( getChildAt(0), numChildren - 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;
    }
  }
}

See Also

Recipes 6.1 and 6.2

Section 6.4: Creating Custom Visual Classes

Problem

You want to create a new type of DisplayObject.

Solution

Create a new class that extends DisplayObject or one of its subclasses so it can be added into a display object container via addChild( ) or addChildAt( ).

Discussion

Among the benefits of moving toward the display list model is the ease of creating new visual classes. In the past, it was possible to extend MovieClip to create custom visuals, but there always had to be a MovieClip symbol in the library linked to the ActionScript class to create an on-screen instance via attachMovie( ). Creating a custom visual could never be done entirely in ActionScript. With the display list model, the process has been simplified, allowing you to do everything in pure ActionScript code in a much more intuitive manner.

In the display list model, as discussed in the introduction of this chapter, there are many more display classes available besides just MovieClip. Before you create your custom visual, you need to decide which type it is going to be. If you're just creating a custom shape, you'll want to extend the Shape class. If you're creating a custom button, you'll probably want to extend SimpleButton. If you want to create a container to hold other display objects, Sprite is a good choice if you don't require the use of a timeline. If you need a timeline, you'll need to subclass MovieClip.

All of the available display object classes are tailored for specific purposes. It's best to decide what purpose your own visual class is going to serve, and then choose the appropriate parent class based on that. By choosing the parent class carefully you optimize size and resource overhead. For example, a simple Circle class doesn't need to subclass MovieClip because it doesn't need the timeline. The Shape class is the better choice in this case because it's the most lightweight option that appropriately fits the concept of a circle.

Once the base class has been decided, all you need to do is write the code for the class. Let's follow through with the circle example and create a new Circle class that extends the Shape display object. In a new ActionScript file named Circle.as, enter the following code:

package {
  import flash.display.Shape;

  /* The Circle class is a custom visual class */
  public class Circle extends Shape {
  
    // Local variables to store the circle properties
    private var _color:uint;
    private var _radius:Number;
    
    /*
     * Constructor: called when a Circle is created. The default
     * color is black, and the default radius is 10.
     */
    public function Circle( color:uint = 0x000000, radius:Number = 10 ) {
      // Save the color and radius values
      _color = color;
      _radius = radius;
      
      // When the circle is created, automatically draw it
      draw( );
    }
    
    /*
     * Draws the circle based on the color and radius values
     */
    private function draw( ):void {
      graphics.beginFill( _color );
      graphics.drawCircle( 0, 0, _radius );
      graphics.endFill( );
    }
  }
}

The preceding code defines a new Circle display object. When a Circle instance is created, you can specify both a color and a radius in the constructor. Methods from the Drawing API (discussed in Recipe 7.3) are used to create the body of the circle with the graphics property, which is inherited from the superclass Shape.

It is always a good idea to separate all drawing code into a separate draw( ) method. The constructor for Circle does not draw the circle directly, but it calls the draw( ) method to create the visual elements.

All that is left to do is create new instances of our custom Circle class and add them to the display list with addChild( ) or addChildAt( ) so they appear on-screen. To create new instances of the class, use the new keyword. The following code example creates a few Circle instances and displays them on the screen:

package {
  import flash.display.Sprite;
  public class UsingCircleExample extends Sprite {
    public function UsingCircleExample( ) {
      // Create some circles with the Circle class and
      // change their coordinates so they are staggered
      // and aren't all located at (0,0).
      var red:Circle = new Circle( 0xFF0000, 10 );
      red.x = 10;
      red.y = 20;
      var green:Circle = new Circle( 0x00FF00, 10 );
      green.x = 15;
      green.y = 25;
      var blue:Circle = new Circle( 0x0000FF, 10 );
      blue.x = 20;
      blue.y = 20;
        
      // Add the circles to the display list
      addChild( red );
      addChild( green );
      addChild( blue );
    }
  }
}

See Also

Recipes 6.1 and 7.3

Section 6.5: Creating Simple Buttons

Problem

You want to create an interactive button that enables a user to click and perform an action, such as submitting a form or calculating a total.

Solution

Create an instance of the SimpleButton class and create display objects for upState, downState, overState, and hitTestState. Alternatively, create a subclass of SimpleButton that describes your desired button behavior.

Use the click event to invoke a method whenever the user presses the button.

Discussion

The display list model provides an easy way to create buttons through the SimpleButton class. The SimpleButton class allows a user to interact with the display object using their mouse, and makes it easy for you to define that interaction through various button states. The possible button states, listed here, are available as properties of the SimpleButton class:

upState
A display object for the default "up" state of the button. The "up" state is shown whenever the mouse is not over the button.
overState
A display object that determines what the button looks like when the mouse moves over the button. When the mouse leaves the button area, the button moves back to the "up" state.
downState
A display object that's shown when the button is pressed (or clicked) "down". When the button is in the "over" state, the "down" state displays when the user presses the left mouse button.
hitTestState
A display object that defines a button's bounds. When the mouse moves inside of the button's hit area, the button enters the "over" state. The hitTestState is typically set to the same display object as the upState. The hitTestState is never actually displayed on-screen; it is only used for mouse tracking purposes.

Pages: 1, 2, 3, 4

Next Pagearrow