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

A button's state is handled by the SimpleButton class, and is based on movement of the user's mouse. You don't have control over setting the internal button state (up, down, or over). Rather, you can only control which display object should appear when the button is in a particular state. By setting the state properties to different display objects, you can provide feedback to the user as they interact with the button using their mouse.

The following example creates a new SimpleButton instance and defines button states using the four state properties defined earlier. Because each state property of the button needs to be set to a DisplayObject instance, the helper method createCircle( ) is used to create different colored circle shapes to be used for the various button states:

package {
  import flash.display.*;
  import flash.events.*;

  public class SimpleButtonDemo extends Sprite {
    public function SimpleButtonDemo( ) {
      // Create a simple button and configure its location
      var button:SimpleButton = new SimpleButton( );
      button.x = 20;
      button.y = 20;
      
      // Create the different states of the button, using the 
      // helper method to create different colors circles
      button.upState = createCircle( 0x00FF00, 15 );
      button.overState = createCircle( 0xFFFFFF, 16 );
      button.downState = createCircle( 0xCCCCCC, 15 );
      button.hitTestState = button.upState;
      
      // Add an event listener for the click event to be notified
      // when the user clicks the mouse on the button
      button.addEventListener( MouseEvent.CLICK, handleClick );
      
      // Finally, add the button to the display list
      addChild( button );  
    }
    
    // Helper function to create a circle shape with a given color
    // and radius
    private function createCircle( color:uint, radius:Number ):Shape {
      var circle:Shape = new Shape( );
      circle.graphics.lineStyle( 1, 0x000000 );
      circle.graphics.beginFill( color );
      circle.graphics.drawCircle( 0, 0, radius );
      circle.graphics.endFill( );
      return circle;
    }
    
    // Event handler invoked whenever the user presses the button
    private function handleClick( event:MouseEvent ):void {
      trace( "Mouse clicked on the button" );  
    }
  }
}

After running the preceding code block, a green circle appears in the movie. When you move your mouse over the green circle, a slightly bigger white circle appears as a rollover. When you click the white circle, it turns into a slightly smaller gray circle. This visual effect is created by the SimpleButton instance changing its state based on the actions of your mouse, switching between the display objects defined in the four state properties.

To listen for events from the button instance, the addEventListener( ) method is used as described in Recipe 1.5. The click event, specified with MouseEvent.CLICK, is handled in the preceding code by the handleClick( ) method. Anytime the user clicks the button instance, the handleClick( ) method is invoked, allowing certain actions to take place. In this simple example, a short message ("Mouse clicked on the button") is displayed to the console.

The hitTestState property is perhaps the most interesting of the button's state properties. You'll notice that the preceding code sets the hitTestState to be the same display object that defines the upState. It is typical to do this because buttons should be activated when the user's mouse is within the bounds of the upState display object.

Although the hitTestState is never visible, failure to set the hitTestState to a display object results in a button that can't be interacted with. Always remember to set the hitTestState of your SimpleButton, even if you simply set it to the same value as upState.

The hitTestState can be set to any display when you'd like to control the active bounds of a button. To create a larger hit area for the button, try modifying the previous code segment to set the hitTestState via this line:

button.hitTestState = createCircle( 0x000000, 50 );

When running this example, you'll notice that the button displays the white "over" circle before the mouse even enters the area of the green circle, contrary to previous behavior. This is because the hit area was increased to a circle of radius 50, giving a larger target area for the user's mouse. You might also notice that black (0x000000) was specified as the color for the hit area circle. This was done on purpose to reinforce the fact that the hit area display object is never visible.

An alternate approach to creating a SimpleButton and setting the four display states for every button is to create a subclass of SimpleButton that defines your button's visual style and creates instances of that instead. Recipe 6.4 describes how to create new visual classes. Following this technique, you can create your own version of a SimpleButton, making it easier to add multiple buttons to your movie.

The following code creates a new RectangleButton class. The RectangleButton class defines the behavior for a special type of SimpleButton that draws a green rectangle with some text on top of it:

package {
  import flash.display.*
  import flash.text.*;
  import flash.filters.DropShadowFilter;

  public class RectangleButton extends SimpleButton {
    // The text to appear on the button
    private var _text:String;
    // Save the width and height of the rectangle
    private var _width:Number;
    private var _height:Number;
    
    public function RectangleButton( text:String, width:Number, height:Number ) {
      // Save the values to use them to create the button states
      _text = text;
      _width = width;
      _height = height;
      
      // Create the button states based on width, height, and text value
      upState = createUpState( );
      overState = createOverState( );
      downState = createDownState( );
      hitTestState = upState;
    }
  
    // Create the display object for the button's up state
    private function createUpState( ):Sprite {
      var sprite:Sprite = new Sprite( );
      
      var background:Shape = createdColoredRectangle( 0x33FF66 );
      var textField:TextField = createTextField( false );
          
      sprite.addChild( background );
      sprite.addChild( textField );

      return sprite;
    }
    
    // Create the display object for the button's up state
    private function createOverState( ):Sprite {
      var sprite:Sprite = new Sprite( );
      
      var background:Shape = createdColoredRectangle( 0x70FF94 );
      var textField:TextField = createTextField( false );
            
      sprite.addChild( background );
      sprite.addChild( textField );

      return sprite;
    }
    
    // Create the display object for the button's down state
    private function createDownState( ):Sprite {
      var sprite:Sprite = new Sprite( );
      
      var background:Shape = createdColoredRectangle( 0xCCCCCC );
      var textField:TextField = createTextField( true );
      
      sprite.addChild( background );
      sprite.addChild( textField );
      
      return sprite;
    }
    
    // Create a rounded rectangle with a specific fill color
    private function createdColoredRectangle( color:uint ):Shape {
      var rect:Shape = new Shape( );
      rect.graphics.lineStyle( 1, 0x000000 );
      rect.graphics.beginFill( color );
      rect.graphics.drawRoundRect( 0, 0, _width, _height, 15 );
      rect.graphics.endFill( );
      rect.filters = [ new DropShadowFilter( 2 ) ];
      return rect;
    }
    
    // Create the text field to display the text of the button
    private function createTextField( downState:Boolean ):TextField {
      var textField:TextField = new TextField( );
      textField.text = _text;
      textField.width = _width;
            
      // Center the text horizontally
      var format:TextFormat = new TextFormat( );
      format.align = TextFormatAlign.CENTER;
      textField.setTextFormat( format );
      
      // Center the text vertically
      textField.y = ( _height - textField.textHeight ) / 2;
      textField.y -= 2;  // Subtract 2 pixels to adjust for offset
      
      // The down state places the text down and to the right
      // further than the other states
      if ( downState ) {
        textField.x += 1;
        textField.y += 1;
      }
      
      return textField;
    }
  }
}

Because all of the button drawing is encapsulated into its own reusable class, creating new button instances is much easier. Instead of having to create a SimpleButton and define the button states by hand for each instance, you can simply create a new RectangleButton instance and add that to the display list.

The following example shows how to create three different rectangular buttons using this new instance:

package {
  import flash.display.*;
  public class SimpleButtonDemo extends Sprite {
    public function SimpleButtonDemo( ) {
      
      // Create three rectangular buttons with different text and 
      // different sizes, and place them at various locations within 
      // the movie
      
      var button1:RectangleButton = new RectangleButton( "Button 1", 60, 100 );
      button1.x = 20;
      button1.y = 20;
      
      var button2:RectangleButton = new RectangleButton( "Button 2", 80, 30 );
      button2.x = 90;
      button2.y = 20;
      
      var button3:RectangleButton = new RectangleButton( "Button 3", 100, 40 );
      button3.x = 100;
      button3.y = 60;
      
      // Add the buttons to the display list so they appear on-screen
      addChild( button1 );
      addChild( button2 );
      addChild( button3 );
    }
  }
}

See Also

Recipes 1.5, 6.1, 6.4, and 6.8

Section 6.6: Loading External Images at Runtime

Problem

You want to load an external image into a movie while it plays.

Solution

Use the new Loader class to load an image ( .jpg, progressive .jpg, .png, or .gif) and display it on-screen.

Discussion

Recipe 9.17 demonstrates how to embed external assets into a movie at compile time via the [Embed] metadata tag. To load external images or movies at runtime during the playback of a .swf, the Loader class needs to be used.

The flash.display.Loader class is very similar to the flash.net.URLLoader class discussed in Recipe 19.3. One of the key differences is that Loader instances are able to load external images and movies and display them on-screen, whereas URLLoader instances are useful for transferring data.

There are three fundamental steps for loading external content:

  1. Create an instance of the Loader class.
  2. Add the Loader instance to the display list.
  3. Call the load( ) method to pull in an external asset.


The load( ) method of the Loader class is responsible for downloading the image or .swf file. It takes a single URLRequest object as a parameter that specifies the URL of the asset to download and display.

The following is a small example of using a Loader instance to download an image named image.jpg at runtime. The code in the LoaderExample constructor has been commented to coincide with the three basic loading steps previously outlined:

package {
  import flash.display.*;
  import flash.net.URLRequest;
  public class LoaderExample extends Sprite {
    public function LoaderExample( ) {
      // 1. Create an instance of the Loader class
      var loader:Loader = new Loader( );
      // 2. Add the Loader instance to the display list
      addChild( loader );
      // 3. Call the load( ) method to pull in an external asset
      loader.load( new URLRequest( "image.jpg" ) );
    }
  }
}

When running this code, the Flash Player looks for image.jpg in the same directory that the .swf movie is being served from because the URLRequest object uses a relative URL. Either a relative or absolute URL can be used to point to the location of the target to load, but the actual loading of the asset is governed by Flash Player's security sandbox, as discussed in Recipe 3.12. As soon as the asset has downloaded, it is automatically added as a child of the Loader instance.

When loading external assets, it's possible that something could go wrong during the loading process. For instance, perhaps the URL is pointing to the incorrect location due to a spelling mistake, or there's a security sandbox violation that won't allow the asset to be loaded. Or, it's possible that the asset is large and is going to take a long time download. Rather than just having an empty screen while the asset downloads, you'd like to show a preloader to inform the user of the download progress.

In these situations, you should add event listeners to the contentLoaderInfo property of the Loader instance to be able to respond to the different events as they occur. The contentLoaderInfo property is an instance of the flash.display.LoaderInfo class, designed to provide information about the target being loaded. The following is a list of useful events dispatched by instances of the LoaderInfo class and what those events mean:

open
Generated when the asset has started downloading.
progress
Generated when progress has been made while downloading the asset.
complete
Generated when the asset has finished downloading.
init
Generated when the properties and methods of a loaded external .swf are available.
httpStatus
Generated when the status code for a failed HTTP request is detected when attempting to load the asset.
ioError
Generated when a fatal error occurs that results in an aborted download, such as not being able to find the asset.
securityError
Generated when data you're trying to load resides outside of the security sandbox.
unload
Generated when either the unload( ) method is called to remove the loaded content or the load( ) method is called again to replace content that already has been loaded.

The following example demonstrates listening for the various download progress related events when loading an image:

package {
  import flash.display.*;
  import flash.text.*;
  import flash.net.URLRequest;
  import flash.events.*;

  public class LoaderExample extends Sprite {
    public function LoaderExample( ) {
      // Create the loader and add it to the display list
      var loader:Loader = new Loader( );
      addChild( loader );
      
      // Add the event handlers to check for progress
      loader.contentLoaderInfo.addEventListener( Event.OPEN, handleOpen );
      loader.contentLoaderInfo.addEventListener( ProgressEvent.PROGRESS, handleProgress );
      loader.contentLoaderInfo.addEventListener( Event.COMPLETE, handleComplete );
      
      // Load in the external image
      loader.load( new URLRequest( "image.jpg" ) );
    }
    
    private function handleOpen( event:Event ):void {
      trace( "open" );
    }
    
    private function handleProgress( event:ProgressEvent ):void {
      var percent:Number = event.bytesLoaded / event.bytesTotal * 100;
      trace( "progress, percent = " + percent );
    }
    
    private function handleComplete( event:Event ):void {
      trace( "complete" );
    }
  }
}

When running the preceding code, you'll see the open message appear in the console window followed by one or more progress messages displaying the current percent loaded, followed by the complete message signaling that the download finished successfully.

By placing code in the event handlers for these events, you can show the progress of a download as it's being loaded. For instance, the handleOpen( ) method would be in charge of creating the preloader and adding it to the display list. The handleProgress( ) method would update the percentage value of the preloader, such as setting the text of a TextField instance to the percent value. Finally, the handleComplete( ) method would perform "clean up" and remove the preloader, since the asset is fully downloaded. Focusing on those methods, the code might look something like this:

private function handleOpen( event:Event ):void {
  // Create a simple text-based preloader and add it to the 
  // display list
  _loaderStatus = new TextField( );
  addChild( loaderStatus );
  _loaderStatus.text = "Loading: 0%";
}

private function handleProgress( event:ProgressEvent ):void {
  // Update the loading % to inform the user of progress
  var percent:Number = event.bytesLoaded / event.bytesTotal * 100;
  _loaderStatus.text = "Loading: " + percent + "%";
}

private function handleComplete( event:Event ):void {
  // Clean up - preloader is no longer necessary
  removeChild( loaderStatus );
  _loaderStatus = null;
}

The preceding code snippet assumes that there is a _loaderStatus variable as part of the class, with type TextField:

private var _loaderStatus:TextField;

Instead of messages appearing via the trace( ) statement as before, modifying the event handlers allows the loading information to be presented inside of the movie itself. This allows users to see the information directly and provides a better experience when loading large external assets.

See Also

Recipes 3.12, 6.7, 9.17, and 19.3

Pages: 1, 2, 3, 4

Next Pagearrow