Memory

Let’s say it one more time: the memory on mobile devices is limited. If you exceed it, Android will terminate your application.

Creating Objects

Choose the appropriate object. For instance, use a Sprite instead of a MovieClip if you don’t need multiple frames. Use a Vector, which is more efficient than an Array, to store objects. Call the getSize() function to determine the memory footprint for a particular data type.

Allocating new blocks of memory is costly. Create your objects at an idle time, ideally when your application first initializes, and reuse them throughout your application.

Object pooling creates a pool to store objects and re-use them over time. In this example, we pop an object, or create one if none exists, and then push it back when done until the next use:

[code]

import flash.display.Sprite;
var pool:Vector.<Sprite>();
// get a new Sprite
var sprite:Sprite = popSprite();
sprite.init(); // set properties as needed
addChild(sprite);
function popSprite():Sprite {
var sprite:Sprite;
if (pool.length > 0) {
sprite = pool.pop(); } else {
sprite = new Sprite();
}
return sprite;
}

[/code]

When the object is no longer needed, return it:

[code]

removeChild(sprite);
pushSprite(sprite);
function pushSprite(sprite:Sprite):void {
pool.push(sprite);
}

[/code]

This approach is organic. The application creates objects as needed until it reaches a sufficient quantity to recycle them. Another method is to create all the objects initially, up to a maximum amount.

Removing Objects

Memory is dynamic. As objects are deleted, memory is released via a process called garbage collection.

Only primitive types, such as String, Number, and Boolean, are used directly. All other types are used using a reference, which must be removed to clear them from memory. This includes removing a child from a displayList, splicing an Array array or a Vector, stopping and deleting a Timer, removing an EventListener, or nulling a reference from another object.

Once this is all done, set the object to null to help the garbage collector work more quickly.

Use the disposeXML method for an XML object. It traverses the tree and sets all pointers between parents and children to null to make the object available for immediate garbage collection:

[code]

import flash.system.System;
function onXMLLoaded(event:Event):void {
var xml:XML = event.target.data;
// sudo code to parse and store data
var dataStorage = parseXML(XML);
System.disposeXML(xml);
xml = null;
}

[/code]

Use the dispose method for a BitmapData object:

[code]

var bitmapData:BitmapData = new BitmapData(480, 800);
bitmapData.dispose();
bitmapData = null;

[/code]

One of the principal reasons for memory leaks is lingering event listeners. A common recommendation today is to set weak event listeners as shown in the code below. Weak references are not counted by the garbage collector as references, a topic we will cover next:

[code]

var sprite:Sprite = new Sprite();
// strongly referenced listeners
sprite.addEventListener(MouseEvent.CLICK, onClick);
// weakly referenced listeners
// eventName, listener, capturePhase, priority, useWeakReference
sprite.addEventListener(MouseEvent.CLICK, onClick, false, 1, true);

[/code]

Use this approach with caution. Too many developers tend to rely on it instead of removing listeners in code. As a responsible coder, you should be diligent about doing the proper housekeeping.

If your code is large, or difficult to maintain, you can create an array to store listeners or a mechanic to automate their removal.

Create a destroy method to use in your classes to remove listeners and objects. To enforce the practice, use an IDestroy interface in which all classes need to have the destroy method but can implement it according to their individual needs:

[code]

// interface
public interface IDestroy {
function destroy():void;
}
// class A
public class ClassA implements IDestroy {
public function ClassA() {
}
public function destroy():void {
// remove listeners
// clear display list
while (numChildren > 0) {
removeChildAt(0);
}
}
}
// class B
public class ClassB implements IDestroy {
public function ClassB() {
}
public function destroy(event:):void {
// empty arrays
// set variables to null
// call garbage collection
System.gc();
}
}

[/code]

If one of your classes implements the interface but erroneously does not have a destroy method, you will see the following error:

[code]

Interface method destroy in namespace com:IDestroy not implemented by class
com.ClassC

[/code]

Garbage Collection

Garbage collection is an automatic mechanism that deallocates the memory used by objects when they are no longer referenced in the application. It is important because it releases memory for future use, and particularly so for mobile devices with limited memory.

The garbage collector is not predictable, but you can sometimes monitor it by watching your memory consumption using the System class. totalMemory refers to the portion of memory allocated to your code. privateMemory is the entire memory consumption of the application. All functions return bytes; divide by 1,024 to obtain kilobytes:

[code]

import flash.system.System;
System.privateMemory/1024;
System.totalMemory/1024;
System.totalMemoryNumber/1024 // returned as NUMBER for greater precision
System.freeMemory/1024; // memory unused

[/code]

You can call the garbage collector directly in AIR, but it requires significant memory and can result in a noticeable slowdown in the application. For this reason, reuse objects or clean them up quickly and frequently as described earlier:

[code]

System.gc();

[/code]

The AIR runtime uses two different methods for best results.

Reference count keeps track of the object and marks it for deletion when it has a value of zero if it has no reference:

[code]

var a:Object = new Object(); count is 1 for a;
var b:Object = a; 2 for b;
a = null; 1 for a;
b = null; 0 for b;

[/code]

This method fails when two objects reference each another:

[code]

var a:Object = new Object(); is 1 for a;
var b:Object = new Object(); is 1 for b;
a.b = b; 2 for b;
b.a = a; 2 for a;
a = null; 1 for a;
b = null; 1 for b;

[/code]

Mark sweeping handles this issue in a more accurate way but takes longer and does not happen as often. It traverses the entire application tree and marks an object that has a reference, marks its children, and so on, recursively. Objects that are not marked can be assumed to be eligible for collection.

In AIR for Android, the garbage collector is more iterative and objects are collected more frequently because memory is in short supply. For this reason, be attentive to making sensors and objects with listener member variables, not local variables.

How Does It Run?

Flash is a frame-based system. Knowing how it runs is fundamental to understanding performance issues.

The Concept of Frame

The concept of frame refers to a block of time more than traversing a frame on the traditional timeline, especially now that few developers use the timeline or put code on frames.

Code execution happens first, in the form of events and code; then the screen is rendered, and so on. Even if no ActionScript code needs to be executed, nor displayOb ject transformed, the process always happens.

The elastic racetrack termination encapsulates this notion of a loop process with potential elasticity and irregularity if the process takes longer than expected. The expectation is determined by the frame rate as you defined it. At 24 frames per second, the default rate on mobile devices, a frame has 1/24 of a second to run code and render the screen.

A high frame rate carries out more operations, but this does not imply better performance on mobile devices if they are not able to keep up, and it drains the battery. A lower but consistent frame rate guarantees smoother playback and a better user experience.

The four blocks starting from the left in Figure 19-2 and Figure 19-3 represent the analysis of events first and the execution of your code in response second.

The runtime never attempts to exceed the frame rate, or speed up if it is idle, but it may slow down if one of the two phases takes longer than expected. Some fluctuation is expected. But in the case of a consistently slow frame rate, or an erratic one, look for the bottleneck. In Figure 19-3, rendering slows down the frame rate.

Figure 19-2. One frame in terms of code execution and rendering
Figure 19-2. One frame in terms of code execution and rendering
Figure 19-3. A slower frame rate due to rendering
Figure 19-3. A slower frame rate due to rendering

Calculating the frame rate

Here is some code you can use to calculate your frame rate. To get a relatively accurate frame rate, interact with your application as you expect your audience to. Test it with other native processes running in the background:

[code]

import flash.utils.Timer;
import flash.events.Event;
var frames:int = 0;
var time:Number = new Date().time;
var timer:Timer = new Timer(1000);
timer.addEventListener(TimerEvent.TIMER, onTimer);
timer.start();
this.addEventListener(Event.ENTER_FRAME, onEnterFrame);
function onEnterFrame(event:Event):void {
frames++;
}
function onTimer(event:TimerEvent):void {
var now:Date = new Date();
var lapse:int = now.time – time;
trace(“FPS: ” + frames*1000/lapse);
time = now.time;
frames = 0;
}

[/code]

Improving performance

Because the ActionScript Virtual Machine handles code execution and rendering on a single thread, only one thing can happen at a time. This is why a processor-intensive operation blocks the renderer until completion or a complex rendering may delay the next code execution.

Use asynchronous events, which use a different thread, whenever possible. The operation may take a little bit longer but will resolve some of the bottlenecks.

SQLite operation can be done in synchronous or asynchronous mode. Only one mode can be used at a time:

[code]

SQLConnection.openAsync();
SQLConnection.open();

[/code]

Operations on the filesystem can also be asynchronous. Use such operations for fairly large files:

[code]

fileStream.openAsync(file, FileMode.READ);
fileStream.open(file, FileMode.READ);

[/code]

If your code performs long operations, you can restructure it to execute in chunks instead of all at once.

Rendering related to sensors should be done on EnterFrame, not when the sensor updates, which can be more frequent than the refresh rate and can be irregular. If you use EnterFrame, create a single listener to manage all your objects.

Never use the updateAfterEvent function. It forces rendering for faster animation. On mobile devices, it will reduce your frame rate. If you need extremely high-speed animation, AIR is not the right tool for the job.

Setting the stage quality to low, or toggling settings, is a good option for faster rendering:

[code]

stage.quality = StageQuality.LOW;
stage.quality = StageQuality.HIGH;

[/code]

Components

If you use the Flex framework, try the Tour de Flex application (see Figure 18-4). It is a good starting point for examples using components to develop AIR for Android applications. You can get it from the Android Market.

Figure 18-4. The Tour de Flex application
Figure 18-4. The Tour de Flex application

Flash Builder was initially not suited for mobile development. Components, such as the DataGrid or the Chart, were too complex and too large for the memory footprint.

Some work has been done from the ground up to optimize the framework with Flex Hero. Some components were rewritten to be mobile-optimized. The mobile theme, used when the MobileApplication tag is detected, has larger, touch-friendly controls, including for scroll bars.

The ViewNavigator helps in the development and management of screens and offers transition animations. The TabNavigator is used for subnavigation. The ActionBar is used for global navigation and messaging.

Using ActionScript and bitmaps is recommended over MXML and FXG at runtime.

If you like the convenience of components but prefer pure ActionScript development, Keith Peters has created lightweight and easy-to-use components (see http://www.min imalcomps.com/ and http://www.bit-101.com/blog/?p=2979).

 

Asynchronous Bitmap Decoding

An improvement to AIR 2.6 allows the decoding of bitmaps on a separate thread. To take advantage of this, use the LoaderContext class as follows:

[code]

import flash.system.LoaderContext;
import flash.display.Loader;
var lc:LoaderContext = new LoaderContext();
lc.imageDecodingPolicy = ImageDecodingPolicy.ON_LOAD;
var loader:Loader = new Loader();
loader.load(new URLRequest(“image.jpg”, lc);

[/code]

This improvement also works with Loader.loadBytes().

The asynchronous decoding allows animations to play smoother. For instance, while you have an animation running, you can now load another graphic in the background without concern that the process will temporary interrupt the animation.

Sprite Sheet and Blitting

Xerox PARC first created the bit-block transfer, Bit BLIT for short, for the Smalltalk system in 1975. Blitting is a computer graphics operation. It takes at least one bitmap and combines it with another. It has been a standard technique used since the beginning of the digital gaming industry.

Let’s see how we can use this technology for our purposes.

Blitting

A sprite sheet, also called a tile sheet animation, is a collection of images arranged into a single image. Traditionally, each sprite represents one frame of an animation.

In AIR, especially on mobile devices, the sprite sheet has benefits beyond animation. It can be used for ease of production and to load a single large image rather than monitoring many small loading processes. Also, bitmap manipulation is very quick. Figure 18-3 shows a sprite sheet for a walk cycle.

Figure 18-3. A sprite sheet for a walk cycle
Figure 18-3. A sprite sheet for a walk cycle

The motion is created in code to copy various areas of the image to the screen. All images have the same predictable repetitive width and height, so copying a rectangular region can be automated. Use measurement with integers in powers of two for faster calculation. Verify that the element that is animating is in the same relative position throughout, preferably from the upper left corner or the baseline.

Use the BitmapData.copyPixels method to copy a defined area. Required parameters are the source bitmap, a rectangle, and a destination point. The method copies a rectangular area for a source image to a destination area of the same size.

Let’s import the graphics of the walking man shown in Figure 18-3 as a PNG image:

[code]

var complete:BitmapData;
var walkIndex:int = 0;
const TOTAL:int = 8;
var loader:Loader = new Loader();
loader loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded);
loader.load(new URLRequest(“/assets/walkingMan.png”);
function onLoaded(event:Event):void {
event.target.removeEventListener(Event.COMPLETE, onLoaded);
complete = event.target.content.bitmapData;
}

[/code]

Add an ENTER_FRAME event listener. Change the definition of the rectangle to copy a different segment of the original bitmap, and push these pixels into the BitmapData called cell. Wrap it into a Bitmap to render it to the screen:

[code]

var cell:BitmapData = new BitmapData(CELL_WIDTH, CELL_HEIGHT, true, 0);
stage.addEventListener(Event.ENTER_FRAME, walk);
function walk(event:Event):void {
var rectangle:Rectangle = new Rectangle(
CELL_WIDTH*walkIndex, 0,
CELL_WIDTH+CELL_WIDTH*walkIndex, CELL_HEIGHT);
cell.copyPixels(complete, rectangle, new Point(0, 0));
var bitmap:Bitmap = new Bitmap(cellBitmap);
addChild(bitmap);
walkIndex++;
if (walkIndex == TOTAL) {
walkIndex = 0;
}
}

[/code]

Some open source AIR applications are available to ease the process of creating sprite sheets. Sprite Sheet Maker, by Mike Jones, lets you import separate PNGs, reorganize them as needed, and export them as the final sprite sheet (see http://blog.flashgen.com/ 2010/12/21/sprite-sheet-maker-preview/). SWFSheet, by Keith Peters, imports an .swf file and converts it to a sprite sheet (see http://www.bit-101.com/blog/?p=2939). Keith takes advantage of Flash as an animation tool and shows the .swf running. The number of frames captured can be adjusted, as well as the frame area and the sprite dimension before exporting the final PNG.

 

 

Vector Graphics at Runtime

Vector graphics scale well, and are therefore reusable and reduce production time, but they render slowly.

Scaling

Create your vector art at medium size and resize it on the fly as needed. This is a great technique for multiple-screen deployment. Let’s assume your original art was created for an 800×480 device and the application is detecting a tablet at 1,024×600, resulting in a scale difference of about 30%. Let’s scale up the art:

[code]

var dpi:int = Capabilities.screenDPI;
var screenX:int = Capabilities.screenResolutionX;
var screenY:int = Capabilities.screenResolutionY;
var diagonal:Number = Math.sqrt((screenX*screenX)+(screenY*screenY))/dpi;
// if diagonal is higher than 6, we will assume it is a tablet
if (diagonal >= 6) {
myVector.scaleX = myVector.scaleY = 1.30;
}

[/code]

cacheAsBitmap

If the object’s only transformation is along the axes, use cacheAsBitmap:

[code]

myVector.cacheAsBitmap = true;
this.addEventListener(Event.ENTER_FRAME, moveArt);
function moveArt(event:Event):void {
myVector.x += 1;
}

[/code]

cacheAsBitmapMatrix

To rotate, scale, or alpha the object, use cacheAsBitmapMatrix along with cacheAsBit map: Both are required for the caching to work.

[code]

import flash.geom.Matrix;
myVector.cacheAsBitmap();
myVector.cacheAsBitmapMatrix = new Matrix();
this.addEventListener(Event.ENTER_FRAME, onMoveArt);
function onMoveArt(event:Event):void {
myVector.x += 1;
myVector.rotation += 1;
}

[/code]

Vector to Bitmap

If the object is animated with filters or if multiple vector assets need to be composited, draw them into a bitmap. Here, if the device is a tablet (as determined by the diagonal size), we scale the art and apply a drop shadow filter. We then draw the vector art into a bitmap.

[code]

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.filters.DropShadowFilter;
if (diagonal >= 6) {
myVector.scaleX = myVector.scaleY = 1.30;
}
var shadow:DropShadowFilter = new DropShadowFilter();
shadow.distance = 5;
shadow.angle = 35;
myVector.filters = [shadow];
var bitmapData:BitmapData = new BitmapData(myVector.width, myVector.height);
bitmapData.draw(vector);
var bitmap:Bitmap = new Bitmap(bitmapData);

[/code]

Compositing Vector Graphics

A common use case for vector graphics is the creation of a customized avatar in a virtual world. Discrete parts, such as hair, shirt, and shoes, are separate assets. They can be changed and saved at runtime. The avatar, however, animates best as a whole, so once the avatar information is collected and all the discrete parts are loaded, they can be composited as one single object.

Use the BitmapData.draw method to render vector graphics into a bitmap. The first parameter, the only one required, is the source, bitmap, or vector. The following parameters are a Matrix, a ColorTransform, a blend mode, a Rectangle, and smoothing (only for the BitmapData source).

In this example, we are using the Matrix to scale the assets down to half their original size. Define the dimension of the BitmapData object used to store the pixels of the assets loaded and a Matrix:

[code]

import flash.display.BitmapData;
import flash.geom.Matrix;
const CELL_WIDTH:int = 50;
const CELL_HEIGHT:int = 100;
var composite:BitmapData = new BitmapData(CELL_WIDTH, CELL_HEIGHT, true, 0);
var matrix:Matrix = new Matrix(0.50, 0, 0, 0.50, 0, 0);

[/code]

Create a Vector to store the path of the assets. The art must be loaded in the order it will be composited, as you would with layers in Flash or Photoshop:

[code]

var assets:Vector.<String> = new Vector.<String>;
assets[0] = PATH_SKIN_ASSET;
assets[1] = PATH_HAIR_ASSET;
assets[2] = PATH_SHIRT_ASSET;
var counter:int = 0;

[/code]

Load one image at a time using a Loader object. A counter variable is used to traverse the Vector and load all the assets. Every time one is available in AIR, it is converted to a bitmap and added to BitmapData. Note that the draw method has the alpha argument set to true to preserve the area of the vector without pixels so that it remains transparent:

[code]

import flash.display.Loader;
import flash.net.URLRequest;
import flash.events.Event;
var loader:Loader = new Loader();
loader loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded);
loading();
function loading():void {
loader.load(new URLRequest(assets[counter]));
}
function onLoaded(event:Event):void {
composite.draw(event.target.content, matrix);
counter++;
if (counter < assets.length) {
loading();
} else {
loader.contentLoaderInfo.removeEventListener(
Event.COMPLETE, onLoaded);
display();
}
}

[/code]

Once all the assets are loaded and composited, create a Bitmap to render the image to the screen:

[code]

import flash.display.Bitmap;
function display():void {
var bitmap:Bitmap = new Bitmap(composite);
addChild(bitmap);
}

[/code]

MovieClip with Multiple Frames

Art production may require the use of a movie clip with multiple frames. This is a familiar workflow for many designers while also having the flexibility of being resizable.

Converting a MovieClip with vector art to a bitmap for better rendering is a good practice, but neither cacheAsBitmap nor cacheAsBitmapMatrix works for a MovieClip with multiple frames. If you cache the art on the first frame, as the play head moves, the old bitmap is discarded and the new frame needs to be rasterized. This is the case even if the animation is just a rotation or a position transformation.

GPU rendering is not the technique to use for such a case. Instead, load your Movie Clip without adding it to the display list.

This time, we need to load a single .swf file and traverse its timeline to copy each frame. Load the external .swf file comprising 10 frames:

[code]

var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
loader.load(new URLRequest(PATH_TO_SWF));

[/code]

Create a rectangle for the cell at a predefined size. Traverse the MovieClip, and use draw, as before, to copy the vector into a bitmap. Then use copyPixels to add the new pixels into another BitmapData that is the width of the total number of frames:

[code]

function onLoadComplete(event:Event):void {
event.target.removeEventListener(Event.COMPLETE, onLoaded);
var mc:MovieClip = event.target.content as MovieClip;
var totalWidth:int = mc.totalFrames*CELL_WIDTH;
var complete:BitmapData =
new BitmapData(totalWidth, CELL_HEIGHT, true, 0);
var rectangle:Rectangle = new Rectangle(0, 0, CELL_WIDTH, CELL_HEIGHT);
var bounds:int = mc.totalFrames;
for (var i:int = 0; i < bounds; i++) {
mc.gotoAndStop(i+1);
var frame:BitmapData =
new BitmapData(CELL_WIDTH, CELL_HEIGHT, true, 0);
frame.draw(mc, scaleMatrix);
complete.copyPixels(image, rectangle, new Point(i*CELL_WIDTH, 0));
}
frame.dispose();
loader.unloadAndStop(mc);
mc = null;

[/code]

Display the large BitmapData into a Bitmap to render it to the screen:

[code]

var bitmap:Bitmap = new Bitmap(complete);
bitmap.smoothing = true;
addChild(bitmap);

[/code]

Use the reverse process to display a single frame. Here we show the third frame:

[code]

var cellBitmap:BitmapData = new BitmapData(CELL_WIDTH, CELL_HEIGHT, true, 0);
var rectangle:Rectangle =
new Rectangle(CELL_WIDTH*3, 0, CELL_WIDTH+CELL_WIDTH*3, CELL_HEIGHT);
cellBitmap.copyPixels(complete, rectangle, new Point(0, 0));
var bitmap:Bitmap = new Bitmap(cellBitmap);
addChild(bitmap);

[/code]

Next we will cover creating an animation from a sprite sheet bitmap.

 

 

 

Text

Text should be a particular concern. The absence of a physical keyboard introduces a new interface and user experience. Embedding and rendering fonts affects size and performance.

The Virtual Keyboard

On most devices, pressing on an input text field brings up the virtual keyboard. AIR for Android only uses the Android default alphabet keyboard.

Be aware of the space the keyboard occupies. The stage position is automatically adjusted to keep the text field visible. If the text field is toward the bottom, the application moves the stage up. To dismiss the virtual keyboard, the user usually needs to tap on the stage. Make sure you leave a noninteractive area for the user to tap on.

If you want to overwrite the default behavior, set the softKeyboardBehavior tag of the application descriptor to none.

[code]

<softKeyboardBehavior>none</softKeyboardBehavior>

[/code]

To control how the application moves, set a listener on the softKeyboardActivating event, which is dispatched when the keyboard opens. Use the softKeyboardRect property of the stage, which contains the dimensions of the area covered by the keyboard:

[code]

import flash.events.SoftKeyboardEvent;
import flash.text.TextField;
import flash.text.TextFieldType;
var textField:TextField = new TextField();
textField.type = TextFieldType.INPUT;
textField.width = 400;
textField.height = 200;
addChild(textField);
textField.addEventListener
(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, onKeyboard);
textField.addEventListener
(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, onKeyboard);
function onKeyboard(event:SoftKeyboardEvent):void {
trace(stage.softKeybardRect.y);
trace(stage.softKeybardRect);
}

[/code]

For fullscreen mode, use the keyboard dimensions as an approximation. The values returned may not be perfectly exact.

Fonts

Try to use device fonts for input text fields, as they render faster than embedded fonts. The Font Embedding dialog box monitor in Flash Professional CS5 and later monitors font usage in your application. You can also generate a size report that lists all the assets, including fonts. Only include the font families you use.

The choice of Android font families is limited but well targeted to the Android style. Figure 18-1 shows the Droid Serif font, created by Steve Matteson of Ascender Corporation.

With AIR 2.6 and up, support is provided for scrolling text; text selection for cut, copy, and paste; and context menus.

Consider using an alternative to input text fields, such as already populated fields or plus and minus buttons for digits. The recommended font size is at least 14 pixels, so that text is readable on high-density devices.

The Flash Text Engine

An application with impeccable typography stands out. The Text Layout Framework (TLF) provides the tooling for text quality but is heavy and not yet ready for mobile.

Figure 18-1. The Droid Serif font
Figure 18-1. The Droid Serif font

The Flash Text Engine (FTE) is the low-level API below the TLF. It is light and renders exquisite script with great precision. It is not as immediately accessible as other tools, however. For simplicity, use it for read-only text and keep the classic TextField object for input text if needed.

Here is a “Hello world” example:

[code]

import flash.text.engine.*;
var fd:FontDescription = new FontDescription();
var ef:ElementFormat = new ElementFormat(fd);
var te:TextElement = new TextElement(“Hello world”, ef);
var tb:TextBlock = new TextBlock();
tb.content = te;
var tl:TextLine = tb.createTextLine(null, 200);
addChild(tl);

[/code]

FontDescription is for the font family. ElementFormat is for styling and layout. TextEle ment is for the content as text and inline graphic. TextBlock is the Factory to create one block of text. Finally, TextLine is the display object for a single line of text. Figure 18-2 depicts the classes needed to create text using the Flash Text Engine.

Figure 18-2. The various classes needed to create text using the Flash Text Engine
Figure 18-2. The various classes needed to create text using the Flash Text Engine

This is a lot of classes for such a simple example, but it introduces the benefit of using this engine. It gives you access to a vast range of typographic settings, bidirectional layout, and support for most scripts. Please refer to the article I wrote on FTE to learn more (see http://www.developria.com/2009/03/flash-text-engine.html).

 

 

 

Case Study

The Album Application

In the mobile version of the AIR application, the user takes a picture or pulls one from the camera roll. She can save it to a dedicated database, along with an audio caption and geolocation information. The group of saved images is viewable in a scrollable menu. Images can be sent from the device to the desktop via a wireless network.

In the desktop version, the user can see an image at full resolution on a large screen. It can be saved to the desktop, and it can also be edited and uploaded to a photo service.

Please download the two applications from this book’s website at http://oreilly.com/ catalog/9781449394820.

Design

The design is simple, using primary colors and crisp type. The project was not developed for flexible layout. It was created at 800×480 resolution with auto-orientation turned off; you can use it as a base from which to experiment developing for other resolutions. The art is provided in a Flash movie to use in Flash Professional or as an .swc file to import into Flash Builder by selecting Properties→ActionScript Build Path→Library Path and clicking on “Add swc.”

Architecture

The source code consists of the Main document class and the model, view, and events packages (see Figure 17-1).

The model package contains the AudioManager, the SQLManager, the GeoService, and the PeerService. The view package contains the NavigationManager and the various views. The events package contains the various custom events.

The SQLManager class is static, so it can be accessed from anywhere in the application without instantiation. The other model classes are passed by reference.

Flow

The flow of the application is straightforward. The user goes through a series of simple tasks, one step at a time. In the opening screen, the OpeningView, the user can select a new picture or go to the group menu of images, as shown in Figure 17-2.

From the AddView page, the user can open the Media Gallery or launch the camera, as shown in Figure 17-3. Both choices go to the same CameraView view. An id parameter is passed to the new view to determine the mode.

The image data received from either of the sources is resized to fit the dimensions of the stage. The user can take another picture or accept the current photograph if satisfied. The image url is sent to the SQLManager to store in its class variable current Photo of type Object, and the application goes to the CaptionView.

In the CaptionView, the user can skip the caption-recording step or launch the Audio Manager. The recording is limited to four seconds and automatically plays the caption sound back. The user can record again or choose to keep the audio. The AudioMan ager compresses the recording as a WAV file and saves it on the SD card. Its url is saved in the SQLManager’s currentPhoto object. The next step is to add geographic information.

Figure 17-1. Packages and classes for the Album application
Figure 17-1. Packages and classes for the Album application
Figure 17-2. The OpeningView
Figure 17-2. The OpeningView

In the GeoView, the user can skip or launch the GeoService. This service creates an instance of the GeoLocation, fetches coordinates, and then requests the corresponding city and country from the Yahoo! API. As in the previous steps, the geodata is saved in the SQLManager’s currentPhoto object. These three steps are shown in Figure 17-4.

Figure 17-3. The AddView, native camera application, and Media Gallery
Figure 17-3. The AddView, native camera application, and Media Gallery
Figure 17-4. The CameraView, CaptionView, and GeoView
Figure 17-4. The CameraView, CaptionView, and GeoView

In the SavingView mode, data saving can be skipped or the data can be saved. For the latter, the SQLManager opens an SQL connection and saves the data, then closes the connection. The application goes back to the OpeningView.

Back at our starting point, another navigation choice is the Group menu. The Menu View page requests the number of images saved for the SQLManager and displays it as a list of items. If the list height is taller than the screen, it becomes scrollable. Selecting one of the items takes the user to the PhotoView screen. The SavingView page and MenuView page are shown in Figure 17-5.

The PhotoView displays the image selected in the MenuView. Choosing to connect calls the PeerService to set up a P2P connection using the WiFi network. Once it is established, the data is requested from the SQLManager using the item ID. The data is then sent. It includes the byteArray from the image, a WAV file for the audio, and the city and country as text. These steps are displayed in Figure 17-6.

Figure 17-5. The SavingView and MenuView
Figure 17-5. The SavingView and MenuView
Figure 17-6. The PhotoView and the steps to send the picture information using a P2P connection
Figure 17-6. The PhotoView and the steps to send the picture information using a P2P connection

Permissions

This application needs the following permissions to access the Internet, write to the SD card, and access GPS sensors, the camera, and the microphone:

[code]

<android>
<manifestAdditions>
<![CDATA[
<manifest>
<uses-permission
android:name=”android.permission.INTERNET”/>
<uses-permission
android:name=”android.permission.WRITE_EXTERNAL_STORAGE”/>
<uses-permission
android:name=”android.permission.ACCESS_FINE_LOCATION”/>
<uses-permission
android:name=”android.permission.ACCESS_COARSE_LOCATION”/>
<uses-permission
android:name=”android.permission.CAMERA”/>
<uses-permission
android:name=”android.permission.RECORD_AUDIO”/>
</manifest>
]]>
</manifestAdditions>
</android>

[/code]

Navigation

The ViewManager class discussed here is almost identical. The flow is a step-by-step process whereby the user can choose to skip the steps that are optional.

Images

The CameraView is used to get an image, either by using the media library or by taking one using the camera. The choice is based on a parameter passed from the previous screen. The process of receiving the bytes, scaling, and displaying the image is the same regardless of the image source. It is done by a utility class called BitmapDataSizing and is based on the dimensions of the screen.

To improve this application, check if an image is already saved when the user selects it again to avoid duplicates.

Audio

The audio caption is a novel way to save a comment along with the image. There is no image service that provides the ability to package an audio commentary, but you could build such an application.

WAV files using the Adobe class WAVReader and then extracted using a third-party library. Here, we create an Album directory on the SD card and a mySounds directory inside it to store the WAV files.

Reverse Geolocation

the process of using geographical coordinates to get an address location such as a city and a street address.

In this application, we are only interested in the city name and country. Therefore, coarse data is sufficient. We do not need to wait for the GPS data to stabilize. As soon as we get a response for the Yahoo! service, we move on to the next step.

SQLite

SQLManager is a static class, so it can be accessed from anywhere in the application. The Main class holds an object which stores information related to a photo until it is complete and ready to be saved:

[code]

var currentPhoto:Object = {photo:””, audio:””, geo:””};

[/code]

The photo property stores the path to where the image is saved in the Gallery. The audio property stores the path to where the WAV file is located and the geo property stores a string with city and country information.

From the SavingView view, the object is saved in the myAlbum.db file on the SD card.

P2P Connection

The peer-to-peer connection is used to send the image, audio caption, and location over a LAN. This example is to demonstrate the potential of what you can do more than a proper use case because the transfer is slow, unless the information is sent in packets and reassembled. This technology is feasible for fairly small amounts of data and has a lot of potential for gaming and social applications.

Once the user has selected an image, she can transfer it to a companion desktop application from the SavingView view. The PeerService class handles the communication to the LAN and the posting of data.

Scrolling Navigation

The MenuView that displays the images saved in the database has scrolling capability if the content is larger than the height of the screen.

There are two challenges to address. The first is the performance of a potentially large list to scroll. The second is the overlapping interactive objects. The scrollable list contains elements that also respond to a mouse event. Both functionalities need to work without conflicting.

We only need to scroll the view if the container is larger than the device height, so there is no need to add unnecessary code. Let’s check its dimensions in the onShow method:

[code]

function onShow():void {
deviceHeight = stage.stageHeight;
container = new Sprite();
addChild(container);
// populate container
if (container.height > deviceHeight) {
trace(“we need to add scrolling functionality”);
}
}

[/code]

If the container sprite is taller than the screen, let’s add the functionality to scroll. Touch events do not perform as well as mouse events. Because we only need one touch point, we will use a mouse event to detect the user interaction. Note that we set cacheAsBit map to true on the container to improve rendering:

[code]

function onShow():void {
if (container.height > deviceHeight) {
container.cacheAsBitmap = true;
stage.addEventListener(MouseEvent.MOUSE_DOWN,
touchBegin, false, 0, true);
stage.addEventListener(MouseEvent.MOUSE_UP,
touchEnd, false, 0, true);
}
}

[/code]

To determine if the mode is to scroll or to click an element that is part of the container, we start a timeout. We will see later why we need this timer in relation to the elements:

[code]

import flash.utils.setTimeout;
var oldY:Number = 0.0;
var newY:Number = 0.0;
var timeout:int;
function touchBegin(event:MouseEvent):void {
oldY = event.stageY;
newY = event.stageY;
timeout = setTimeOut(startMove, 400);
}

[/code]

When the time expires, we set the mode to scrollable by calling the startMove method. We want to capture the position change on MOUSE_MOVE but only need to render the change to the screen on ENTER_FRAME. This guarantees a smoother and more consistent motion. UpdateAfterEvent should never be used in mobile development because it is too demanding for devices:

[code]

function startMove(event:MouseEvent):void {
stage.addEventListener(MouseEvent.MOUSE_MOVE,
touchMove, false, 0, true);
stage.addEventListener(Event.ENTER_FRAME, frameEvent, false, 0, true);
}

[/code]

When the finger moves, we update the value of the newY coordinate:

[code]

function touchMove(event:MouseEvent):void {
newY = event.stageY;
}

[/code]

On the enterFrame event, we render the screen using the new position. The container is moved according to the new position. To improve performance, we show and hide the elements that are not in view using predefined bounds:

[code]

var totalChildren:int = container.numChildren;
var topBounds:int = -30;
function frameEvent(event:Event):void {
if (newY != oldY) {
var newPos = newY – oldY;
oldY = newY;
container.y += newPos;
for (var i:int = 0; i < totalChildren; i++) {
var mc:MovieClip = container.getChildAt(i) as MovieClip;
var pos:Number = container.y + mc.y;
mc.visible = (pos > topBounds && pos < deviceHeight);
}

}
}

[/code]

On touchEnd, the listeners are removed:

[code]

function touchEnd(event:MouseEvent):void {
stage.removeEventListener(MouseEvent.MOUSE_MOVE, touchMove);
stage.removeEventListener(Event.ENTER_FRAME, frameEvent);
}

[/code]

As mentioned before, elements in the container have their own mouse event listeners:

[code]

element.addEventListener(MouseEvent.MOUSE_DOWN, timeMe, false, 0, true);
element.addEventListener(MouseEvent.MOUSE_UP, clickAway, false, 0, true);

[/code]

On mouse down, the boolean variable isMoving is set to false and the visual cue indicates that the element was selected:

[code]

var isMoving:Boolean = false;
var selected:MovieClip;
function timeMe():void {
isMoving = false;
selected = event.currentTarget as MovieClip;
selected.what.textColor = 0x336699;
}

[/code]

On mouse up and within the time allowed, the stage listeners and the timeout are removed. If the boolean isMoving is still set to false and the target is the selected item, the application navigates to the next view:

[code]

function clickAway(event:MouseEvent):void {
touchEnd(event);
clearTimeOut(timeout);
if (selected == event.currentTarget && isMoving == false) {
dispatchEvent(new ClickEvent(ClickEvent.NAV_EVENT,
{view:”speaker”, id:selected.id}));
}

[/code]

Now let’s add to the frameEvent code to handle deactivating the element when scrolling. Check that an element was pressed, and check that the selected variable holds a value and that the motion is more than two pixels. This is to account for screens that are very responsive. If both conditions are met, change the boolean value, reset the look of the element, and set the selected variable to null:

[code]

function frameEvent(event:Event):void {
if (newY != oldY) {
var newPos = newY – oldY;
oldY = newY;
container.y += newPos;
for (var i:int = 0; i < totalChildren; i++) {
var mc:MovieClip = container.getChildAt(i) as MovieClip;
var pos:Number = container.y + mc.y;
mc.visible = (pos > topBounds && pos < deviceHeight);
}
if (selected != null && Math.abs(newPos) > 2) {
isMoving = true;
selected.what.textColor = 0x000000;
selected = null;
}
}
}

[/code]

There are various approaches to handle scrolling. For a large number of elements, the optimal way is to only create as many element containers as are visible on the screen and populate their content on the fly. Instead of moving a large list, move the containers as in a carousel animation and update their content by pulling the data from a Vector or other form of data content.

If you are using Flash Builder and components, look at the Adobe lighthouse package (http://www.adobe.com/devnet/devices/fpmobile.html). It contains DraggableVertical Container for display objects and DraggableVerticalList for items.

Desktop Functionality

The AIR desktop application, as shown in Figure 17-7, is set to receive the data and display it. Seeing a high resolution on a large screen demonstrates how good the camera quality of some devices can be. The image can be saved on the desktop as a JPEG.

Figure 17-7. AIR desktop companion application to display images received from the device
Figure 17-7. AIR desktop companion application to display images received from the device

Another technology, not demonstrated here, is Pixel Bender, used for image manipulation. It is not available for AIR for Android but is for AIR on the desktop. So this would be another good use case where devices and the desktop can complete one another.

Breadcrumb Navigation

You can use different approaches for the user to navigate between screens.

AIR does not have access to the Options menu triggered by pressing the Menu key. If you want to create a native-like look and feel, you can design a similar-looking menu in your application for the application’s key views. The advantage of this approach is that the navigation menu does not take real estate away from your application. It is made visible when pressing the Menu key and collapses after a selection is made.

Another approach is to use breadcrumb navigation. This is a common and intuitive type of interface. It registers the user’s selection as he goes from screen to screen and stores his history. A persistent button gives the user the option to go back. Figure 16-5 shows a breadcrumb navigation flow and view stack.

We only need to make a few changes to add this functionality.

Let’s add the functionality to the BaseView class to include a back button to the dis playList and a callback function to dispatch a goBack event when the back button is pressed. In the onShow function, the onBack method of the super class is called last so that the back button is always at the top level of the display list.

This is the BaseView class:

[code]

import flash.events.Event;
protected function onBack():void {
var backButton:BackButton = new BackButton();
backButton.addEventListener(MouseEvent.CLICK, onGoBack);
addChild(backButton);
}
private function onGoBack(event:MouseEvent):void {
event.stopPropagation();
event.currentTarget.removeEventListener(MouseEvent.CLICK, onGoBack);
removeChild(event.currentTarget as MovieClip);
dispatchEvent(new Event(“goBack”));
}

[/code]

This is the SessionsView class modified to call the onBack method of the BaseView class:

[code]

public function onShow():void {
container = new Sprite();
// request list of sessions if not acquired yet
if (list == null) {
list = Sessions.sessionList;
}
// display sessions
showSessions();
// add back button
super.onBack();
}

[/code]

Figure 16-5. Breadcrumb navigation flow and view stack
Figure 16-5. Breadcrumb navigation flow and view stack

The ViewManager class also needs to be edited. The visited views are stored in a Vec tor. In the setCurrentView function, the new view is pushed to the stack:

[code]

private currentView:BaseView;
private var viewStack:Vector.<BaseView>;
private function goTo(event:ClickEvent):void {
viewStack.push(event.data);
setCurrentView(event.data);
}
private function setCurrentView(object:Object):void {
if (currentView) {
currentView.removeEventListener(ViewEvent.CLICK, goTo);
IView(currentView).onHide();
currentView.removeEventListener(“goBack”, goBack);
timeline.removeChild(currentView);
}
currentView = viewList[object.view];
if (object.id != undefined) {
currentView.setID(object.id);
}
timeline.addChild(currentView);
currentView.addEventListener(ClickEvent.NAV_EVENT, goTo, false, 0, true);
currentView.addEventListener(“goBack”, goBack, false, 0, true);
IView(currentView).onShow();
}

[/code]

When the back button is pressed, the current view is removed from the stack first. The setCurrentView method is then called and the last view stored is passed as an argument:

[code]

private function goBack(e:Event):void {
viewStack.pop();
setCurrentView(viewStack[viewStack.length – 1]);
}

[/code]

You can now easily add views to your application. If you want to add a transition between views, the simplest solution is to take a screen grab of the current view and tween it on a top layer while you are revealing the destination view.

Once this navigation functionality is in place, you can now focus on other aspects, perhaps more abstract or challenging, of your project.

Navigation

Unlike desktop applications, mobile applications only display one screen, or view, at a time. Each screen should be designed for a single task with clear and focused content. Intuitive single-click navigation to move to previous and next steps is key.

We will explore two approaches in this chapter. The first is a navigation system I wrote for my mobile applications using pure ActionScript. The second approach takes advantage of the ViewNavigator in the Flex Hero framework for mobile applications. It includes a few additional features such as transitions and a global ActionBar.

Navigation

You want your audience to be able to move forward between screens without re-creating the same steps over and over. You may need to provide the logic to navigate back to previous screens.

Google discourages the use of the physical back button for anything but going back to the previous application. However, the functionality exists, and some applications, as well as Flash Builder Mobile, use it to go through the stack of views. In my example, I create a back button within the application instead.

ViewManager

I originally developed this code for a conference scheduler application.

Attendees can carry the scheduler in their pocket and organize and save their own schedules. The application’s business logic is fairly simple, but all the screens are interconnected and can be accessed from several different points. For instance, from the session view, the user can access the session’s speaker(s), and from the speaker view, the user can access one of the sessions he is speaking at.

Creating views

The ViewManager creates the different views and manages them during the life of the application.

The document class creates an instance of the ViewManager. It calls its init function and passes a reference of the timeline:

[code]

import view.ViewManager;
// member variable
private var viewManager:ViewManager;
viewManager = new ViewManager();
viewManager.init(this);

[/code]

The ViewManager class stores a reference to the timeline to add and remove views from the display list:

[code]

private var timeline:MovieClip;
public function init(timeline:MovieClip):void {
this.timeline = timeline;
}

[/code]

The ViewManager creates an instance of each view and stores them in a viewList object. The following code assumes the MenuView, SessionsView, and SessionView classes exist.

The initialization process for each view, creation of the view’s member variables and references to other objects, only needs to happen once.

Note that the views are of data type BaseView.

[code]

private var currentView:BaseView;
private viewList:Object = {};
public function init(timeline:MovieClip):void {
this.timeline = timeline;
createView(“menu”, new MenuView());
createView(“sessions”, new SessionsView());
createView(“session”, new SessionView());
}
private function createView(name:String, instance:BaseView):void {
viewList[name] = instance;
}

[/code]

The initial view display

When the application first starts, the document class loads an XML document that contains all the data regarding the conference, such as the list of sessions and speakers. While this is taking place, the ViewManager displays an introductory view without any interactivity. Let’s modify the init method to add this functionality. The setCurrent View method will be discussed in the next paragraph:

[code]

public function init(timeline:MovieClip):void {
this.timeline = timeline;
createView(“intro”, new IntroView());
createView(“menu”, new MenuView());
createView(“sessions”, new SessionsView());
createView(“session”, new SessionView());
setCurrentView({view:”intro”});
}

[/code]

The current view display

Once the data is loaded, parsed, and stored in the model part of the application, the document class calls the onDataReady method on the ViewManager:

[code]

// set up application, model and get data from external xml
viewManager.onDataReady();

[/code]

In turn, the ViewManager defines the new view by calling the setCurrentView method and passes an object with the property view to define the view to display:

[code]

public function onDataReady():void {
setCurrentView({view:”menu”});
}

[/code]

The setCurrentView method removes the previous view if there is one. It then stores the view in the currentView variable and adds it to the display list. Two methods, onHide and onShow, are called via the IView interface, discussed next. Each view uses the methods to clear or add from the display list and destroy objects.

The method also registers the view for a custom ClickEvent.NAV_EVENT with the setCur rentView method of the ViewManager as the callback function.

[code]

import view.ClickEvent;
private var currentView:BaseView;
private function setCurrentView(object:Object):void {
// remove current view
if (currentView) {
currentView.removeEventListener(ClickEvent.NAV_EVENT, goTo);
IView(currentView).onHide();
timeline.removeChild(currentView);
currentView = null;
}
// add new view
currentView = viewList[object.view];
if (object.id != undefined) {
currentView.setID(object.id);
}
currentView.addEventListener(ClickEvent.NAV_EVENT, goTo, false, 0, true);
IView(currentView).onShow();
timeline.addChild(currentView);
}
// pass event data object
private function goTo(event:ClickEvent):void {
setCurrentView(event.data);
}

[/code]

The IView interface

It is imperative that all views have the two methods, onHide and onShow, so we use an IView interface. Each view also needs a method—here it is clickAway—to navigate to the next view. In our application, this always occurs upon user interaction. We will therefore use a MouseEvent:

[code]

package view {
import flash.events.MouseEvent;
public interface IView
{
function onShow():void
function onHide():void
function clickAway(event:MouseEvent):void
}
}

[/code]

Creating a custom event

A custom event is used to pass the destination view and additional data, if needed, from the current view. For example, if the user is looking at the screen displaying all the conference sessions and clicks on a specific session, we use the event object to pass the session ID to the Session view, as illustrated in Figure 16-1:

[code]

{view:”session”, id:5}

[/code]

Figure 16-1. The mechanism to send destination and data from the current view to the destination view via the ViewManager
Figure 16-1. The mechanism to send destination and data from the current view to the destination view via the ViewManager

The custom class is as follows. Its data property is an object, so we can add additional parameters as needed:

[code]

import flash.events.Event;
import events.ClickEvent;
final public calls ClickEvent extends Event {
public static const NAV_EVENT:String = “NavEvent”;
public var data:Object;
public function ClickEvent(type:String, data:Object = null) {
super(type, true, true);
this.data = data;
}
public override function clone():Event {
return new ClickEvent(this.type, this.data);
}
}

[/code]

Individual Views

Let’s examine some of the views.

Inheritance

Some functionality is identical for all views. For this reason, they all inherit from the same super class, called BaseView. Here we declare two member variables, id and con tainer, and a function, setID. It is a simple class initially. We will develop it further in this chapter to add a button and interactivity to navigate in reverse:

[code]

package view {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
public class BaseView extends Sprite
{
protected var id:int;
protected var container:Sprite;
protected function SeID(id:int):void {
this.id = id;
}
}
}

[/code]

The following code is for the MenuView class. It adds three buttons—for sessions, speakers, and a custom schedule—and their listeners on onShow. It clears listeners and empties the display list on onHide. Figure 16-2 shows the Menu view of the AIR scheduler application for the 2010 GoogleIO conference:

[code]

package view {
import flash.events.MouseEvent;
import view.ClickEvent;
final public class MenuView extends BaseView implements IView() {
public function MenuView(){}
public function onShow():void {
var sessions:sessionsBut = new sessionsBut();
sessions.view = “sessions”;
sessions.addEventListener(MouseEvent.CLICK, onClickAway);
var speakers:speakersBut = new speakersBut();
speakers.view = “speakers”;
speakers.addEventListener(MouseEvent.CLICK, onClickAway);
var schedule:scheduleBut = new scheduleBut();
schedule.view = “schedule”;
schedule.addEventListener(MouseEvent.CLICK, onClickAway);
addChild(sessions);
addChild(speakers);
addChild(schedule);
}
public function onHide():void {

while(numChildren > 0) {
getChildAt(0).
removeEventListener(MouseEvent.CLICK, onClickAway);
removeChildAt(0);
}
}
public function onClickAway(event:MouseEvent):void {
dispatchEvent(new ClickEvent(ClickEvent.NAV_EVENT,
{view:event.currentTarget.view});
}
}
}

[/code]

Figure 16-2. The Menu view of the AIR scheduler application for the 2010 GoogleIO conference
Figure 16-2. The Menu view of the AIR scheduler application for the 2010 GoogleIO conference

The next example shows the SessionsView code. Note that the code is minimal to keep the focus on navigation. For instance, the scrolling mechanism was left out.

As mentioned before, the initialization process for each view only needs to happen once for the life of the application. Here the SessionsView class acquires the data for the sessions just once if its list variable is null.

The sessions data is stored in a Vector object called sessionList of the static class Sessions (not covered here) and is populated with objects of data type Session. It is already sorted and organized by day and time:

[code]

package view {
import events.ClickEvent;
import flash.events.MouseEvent;
import model.Sessions; // static class holds list of all Sessions
import model.Session; // class holds individual session data
final public class SessionsView extends BaseView implements IView() {
private var list:Vector.<Session>;
public function SessionsView (){}
]
public function onShow():void {
container = new Sprite();
// request list of sessions if not acquired yet
if (list == null) {
list = Sessions.sessionList;
}
// display sessions
showSessions();
}
}

[/code]

We traverse the list of sessions and display all the sessions with the hardcoded date of 19. Again, this is to keep this example simple. The conference took place over two days and would, in a full example, require a UI to choose between one of the two dates:

[code]

private showSessions():void {
var timeKeeper:String = “0”;
var bounds:int = list.length;
var ypos:int = 50;
for (var i:int = 0; i < bounds; i++) {
var session:Session = list[i];
// display a blue time indicator if it is a new time
if (session.time > timeKeeper) {
timeKeeper = session.time;
ypos += 15;
// TimeBar is a movieclip
var bar:TimeBar = new TimeBar();
bar.y = ypos;
bar.timeInfo.text = timeKeeper;
container.addChild(bar);
ypos += 60;
}
// load the individual session
// it returns its height to position the next element below it
var newPos = loadSession(session, ypos);
ypos =+ (newPos + 10);
}
addChild(container);
}
private loadSession(session:Session, ypos:int):int {
// SessionSmall is a movieclip
var mc:SessionSmall = new SessionSmall();
mc.y = ypos;
mc.id = session.id;
mc.what.autoSize = TextFieldAutoSize.LEFT;
mc.what.text = “+ ” + session.title;
mc.back.height = mc.what.height;
mc.addEventListener(MouseEvent.CLICK, clickAway, false, 0, true);
container.addChild(mc);
// return the session movie clip height
return mc.what.height;
}

[/code]

When the user chooses a session, its ID is passed along with the destination view:

[code]

public function clickAway(event:MouseEvent):void {
dispatchEvent(new ClickEvent(ClickEvent.NAV_EVENT,
{view:”session”, id:event.currentTarget.id}));
}

[/code]

In the onHide method, all the children of the Sprite container are removed as well as their listeners if they have one. Then the container itself is removed. Figure 16-3 shows the sessions broken down by day and time:

[code]

public function onHide():void {
while (container.numChildren > 0) {
var child:MovieClip = container.getChildAt(0) as MovieClip;
if (child.id != null) {
child.removeEventListener(MouseEvent.CLICK, clickAway);
}
container.removeChild(child);
}
removeChild(container);
container = null;
}

[/code]

Here is the SessionView code. The method displays all the data related to a session. This includes the session title, a description, the speakers involved, and the room, category, and rank:

[code]

package view {
import events.ClickEvent;
import flash.events.MouseEvent;
import model.Sessions;
import model.Speakers; // static class that holds Speakers data
final public class SessionView extends BaseView implements IView() {
public function SessionView(){}
public function onShow():void {
// search Sessions by id
var data:Object = Sessions.getItemByID(id);
container = new Sprite();
addChild(container);
// display title and description
// SessionMovie is a movieclip
var session:SessionMovie = new SessionMovie();
session.title.autoSize = TextFieldAutoSize.LEFT;
session.title.text = data.title;
session.body.text = data.description;
container.addChild(session);
// display list of speakers
for (var i:int; i < data.speakers.length; i++) {
var bio:Bio = new Bio();
bio.id = data.speakers[i];
// search list of speakers by id
var bioData:Object = Speakers.getItemByID(bio.id);
bio.speaker.text = bioData.first + ” ” + bioData.last;
bio.addEventListener(MouseEvent.CLICK,
clickAway, false, 0, true);
}
// display category, level, rank and room number
// Border is a movieClip
var border:Border = new Border();
// categories is a movieclip with frame labels matching category
border.categories.gotoAndStop(data.tag);
// rank is a movieclip with a text field
border.rank.text = String(data.type);
// room is a movieclip with a text field
border.room.text = String(data.room);
container.addChild(border);
}

[/code]

Figure 16-3. The sessions broken down by day and time
Figure 16-3. The sessions broken down by day and time

Clicking on one of the speakers takes the user to a new speaker destination view defined by an ID:

[code]

public function clickAway(event:MouseEvent):void {
dispatchEvent(new ClickEvent(ClickEvent.NAV_EVENT,
{view:”speaker”, id:event.currentTarget.id}));
}
}

[/code]

In the onHide method, all the children of the Sprite container are removed as well as their listeners if they have one. Then the container itself is removed:

[code]

public function onHide():void {
while(container.numChildren > 0) {
var child:container.getChildAt(0);
if (child.id != null) {
child removeEventListener(MouseEvent.CLICK, clickAway);
}
container.removeChild(child);
}
removeChild(container);
container = null;
}

[/code]

Figure 16-4 shows a subset of information for a session.

Figure 16-4. A subset of information for a session, including the speaker(s) name, category, level of expertise, and room number
Figure 16-4. A subset of information for a session, including the speaker(s) name, category, level of expertise, and room number