Creating Dynamic Lists in Flex & Flash

In this article I’ll describe what a dynamic list is, go over the basic parts that make up a list, and show you how to build those basic parts.  Then, I’ll identify common list building challenges, and how people have overcome them.  Finally, I’ll show you how to utilize the built-in Flash and Flex classes which allow you to create not just lists, but really powerful dynamic components.  This article is suited for both Flash and Flex.

Introduction

Most Flex and Flash applications have dynamic lists.  This isn’t just a List control, but rather a repeated GUI item that had different data in it.  It could be a List control, a DataGrid, a slideshow, or a 3D table full of dynamic tiles.  What they all have in common is that they are usually built around an ActionScript Array.  Sometimes a list can be built around an Object or XML since both can have sequential data, but the common use case is to use an Array since order is guaranteed, it’s linear, and you actually care about the order of the items.

Most controls/components will take an array and draw something with it.  For example, if you feed a Flash List control a DataProvider (a fancy Array), it’ll show the contents of that DataProvider as text labels in the list.  If there are more items in the DataProvider than the list has vertical room to render, it’ll show a scroll bar.  The Flex DataGrid works the same way using an ArrayCollection (similar to a Flash DataProvider).  Both will draw the first piece of data in your Array first, and the last piece last; aka, lower than the first.  Some lists have the ability to draw horizontally.  The TileList in Flex does both, using rows and columns to illustrate your Array.

The Basics

All lists, whether created by you or using a pre-built component, have 2 criteria they need to know how to draw your Array.

  1. How do I visually represent the data?
  2. How do I show there is more data than is being displayed?

That’s it.  What graphics does it show to show you there are “3 items” in your Array, and what does it show to let you know “even though you only see 8 items, there are more items to see”.

Let’s start with a simple example of rendering 3 items in our Array using a red bordered TextField:

import flash.text.TextField;
var list:Array 		= ["one", "two", "three"];
var startX			= 40;
var startY 			= 40;
var margin 			= 8;
var textHack 		= 4;
for(var i = 0; i < list.length; i++)
{
        var txt 		= new TextField();
        addChild(txt);
        txt.border 		= true;
        txt.borderColor = 0x990000;
        txt.text 		= list[i];
        txt.x 			= startX;
        txt.y 			= startY;
        txt.width 		= txt.textWidth + textHack;
        txt.height 		= txt.textHeight + textHack;

        startY 			+= txt.textHeight + textHack + margin;
}

list-tut-01

Notice what this code is doing. It is looping through an Array, creating a TextField for each item, putting the item into the TextField as text, and finally incrementing the startY. Notice the text shown is using the current index of the for loop to determine what text is shown. The last part, incrementing the startY, is what allows each new TextField to show below the former. In this example, we know that in our vertical list, the first item is the first item in the array, and the last item is the last.

Using just a simple for loop, we now have the work horse of our list rendering code. To see it in action, let’s add 3 more pieces of data to our Array:

var list:Array 	= ["one", "two", "three", "uno", "dos", "tres sucka!"];

 

Now, if you re-run your code, you’ll see 6 items. You could put whatever you wanted to into the Array, as well as as many items as you wanted, and it’ll dynamically draw it.

list-tut-02

Using variables for all of the positioning allows us to finely control how our list is renderered.  Try playing with margin value above, making it larger than 8, or even using negative values.

Custom Item Renderers

An item renderer is the graphics you use to visually represent an item in your Array.  In the above example, I use a red bordered TextField as the item renderer.  For every item in the Array, I draw an item renderer.  If there are 10 items, I’ll draw 10 item renderers.  In the above code, you could swap out the TextField for whatever you wanted.  For example, what if each piece of data corresponded to a graphic we needed?  We could utilize the Array items themselves to be names of the item renderers we want to use.

import flash.display.Sprite;
var list:Array 		= [Box, Triangle, Circle, Circle, Box, Circle, Triangle];
var startX			= 40;
var startY 			= 40;
var margin 			= 8;
for(var i = 0; i < list.length; i++)
{
        var clazz:* 		= list[i];
        var item:Sprite 	= new clazz() as Sprite;
        addChild(item);
        item.x 				= startX;
        item.y 				= startY;

        startY 				+= item.height + margin;
}

 

This updated code put’s 3 types of classes into the Array.  We then take those items and draw them as we get them.  Each one of those classes is a Symbol class in the Library.

Another way is to use the same control and does something with the data.  For example, our TextField actually shows the data as text.  If we had numbers which represented the value of something.

var list:Array 		= [0.1, 9, 1, .7, .5, .2, .3];
var startX			= 40;
var startY 			= 40;
var margin 			= 8;
for(var i = 0; i < list.length; i++)
{
        var range:Range 	= new Range();
        addChild(range);
        var percentage 		= list[i];
        range.gotoAndStop(percentage * 100);
        range.x 			= startX;
        range.y 			= startY;

        startY 				+= range.height + margin;
}

 

Let’s create a miniature progress bar, a 100 frame MovieClip:

list-tut-03

We’ll then use the Range MovieClip as the item renderer, and set it’s frame based on the percentage in our Array.  The result is a series of Progress bars that all visually represent the value in the array. 

list-tut-04

We could even guide out the outline layer in our Range MovieClip, change the orientation 90 degrees, and randomize the data…

 

var startX			= 40;
var startY 			= 40;
var barWidth		= 30;
var barHeight		= 8;
var margin 			= 2;
var max				= 50
for(var i = 0; i < max; i++)
{
        var range:Range 	= new Range();
        addChild(range);
        range.gotoAndStop(Math.round(Math.random() * 100));
        range.x				= startX;
        range.y				= startY;
        range.width			= barWidth;
        range.height		= barHeight;
        range.rotation 		-= 90;

        startX 				+= barHeight + margin;
}

 

…and voila… horizontal bar chart.

list-tut-05

Let’s try the above again with some more complex data, a more complex renderer, and draw using rows and columns.  We’ll use some scientifically correct Halo statistics of members from the Flash community.  First, let’s start venturing into strongly typed data by creating a PersonVO class.  We could just as easily use an Object, like {name: “So and So”, tag: “Dude”, skill: .8, image: “some.jpg”}, but if you mistyped a field name, Flash wouldn’t tell like it would for a class and ActionScript 3 rewards you with speed for strong-typing, so it’s worth it.

package
{
        public class PersonVO
        {
                public var name:String;
                public var tag:String;
                public var skill:Number;
                public var image:String;

                public function PersonVO(name:String, tag:String, skill:Number, image:String):void
                {
                        this.name				= name;
                        this.tag				= tag;
                        this.skill				= skill;
                        this.image				= image;
                }
        }
}

 

We create a more advanced item renderer called “Card”. It has a Flash CS3 image component called “image”, 2 TextFields, and our Range component called “range”.

list-tut-06

We can now create Card MovieClips, and shove data into them. Notice in our new code we’re using 2 for loops, one for rows and one for the columns. We use a counter to act like the old “i” in our old for loop, and we modify the startX and startY differently in each loop to draw in a grid.

var list:Array 		= [
new PersonVO("Mike Chambers", "Long Ass Title...", .2, "mesh.jpg"),
             new PersonVO("Bizznatchwhocodes", "Canuck who codes", .2, "stac.jpg"),
             new PersonVO("Grant Skinner", "Canuck who codes", .2, "skin.jpg"),
             new PersonVO("Jesse Warden", "ZOMG titlez!11!", .99999, "jxl.jpg"),
             new PersonVO("Brandon Hall", "Does he still code?", .2, "bran.jpg"),
             new PersonVO("Steven Sacks", "Created Gaia and a crackhead", 0, "stev.gif")
];

var startX				= 40;
var startY 				= 40;
var margin 				= 20;
var rows				= 3;
var cols				= 2;
var counter				= -1;
for(var r = 0; r < rows; r++)
{
        for(var c = 0; c < cols; c++)
        {
                if(counter + 1 < list.length)
                {
                        counter++;
                        var person:PersonVO		= list[counter];
                        var card 				= new Card();
                        addChild(card);
                        card.x 					= startX;
                        card.y					= startY;
                        card.nameTextField.text = person.name;
                        card.tagTextField.text 	= person.tag;
                        card.range.gotoAndStop(Math.round(person.skill * 100));
                        card.image.source		= person.image;

                        startX 					+= card.width + margin;
                }
                else
                {
                        return;
                }
        }
        startX 	= 40;
        startY += card.height + margin;
}

The end result is a grid of Card MovieClips with PersonVO data in them.

list-tut-07

Changes

All the solutions above are dynamic, but only half-way. The one critical thing they are missing is reacting to the data if it changes later. If I were to add a new piece of data to any of the Array’s above, nothing would happen. The only way to make the dynamic lists accurate is to redraw everything. While AS3 is fast, that’s just ridiculous and won’t scale, there are better ways to write redraw routines. …unless you’re in an agency under a tight deadline, in that case, good luck and Thank God for the new AVM!

When someone, anyone, adds data to an Array, there is no Event raised. Since Array’s are passed by reference, you could potentially have many people adding data to your Array, and you’d never know. Since you can’t know, you can’t effectively redraw your list. Goonies never say die, Flashers never say “can’t”.

Both Flash and Flex have classes that solve this problem; DataProvider in Flash CS3/CS4, and ArrayCollection in Flex. They act just like an Array, yet dispatch events when the data changes, letting you know exactly what changed and where. You have now solved the first problem: knowing when your list’s data provider has changed. The second problem to solve is a more effective redraw mechanism so you don’t have to redraw your entire list. That one is harder, however, so let’s at least see how DataProvider can help us draw lists that can redraw themselves when their source Array (aka data provider) changes first.

Let’s start with something simple. Where Array uses push to add a new item to the end of the Array, a DataProvider uses the addItem function. While they both do the same thing to the underlying data, the DataProvider dispatches an event after you add the data, letting anyone who cares to know that the data has changed. Here, we add a new Cube MovieClip on top of another Cube MovieClip every time we add new data. We only run our redraw function when data is added to the DataProvider.

import flash.display.Sprite;

import fl.data.DataProvider;
import fl.events.DataChangeEvent;
import fl.controls.Button;

// create our list
var list:DataProvider = new DataProvider();
list.addEventListener(DataChangeEvent.DATA_CHANGE, onListChanged);

// create a sprite to hold our cubes
var blockHolder = new Sprite();
addChild(blockHolder);
blockHolder.x = 40;
blockHolder.y = 40;

// create a button to allow us to add things on a whim
var button = new Button();
addChild(button);
button.label = "Add";
button.addEventListener("click", onAdd);
button.x = 20;
button.y = 20;
button.width = 40;

// when we add data to our list, this function runs
function onListChanged(event)
{
        // kill all previous cubes
        if(blockHolder)
        {
                var kizzids = blockHolder.numChildren;
                while(kizzids--)
                {
                        blockHolder.removeChildAt(kizzids);
                }
        }

        // draw anew
        var startX = 40;
        var startY = 60;
        var incrementX = 16;
        var incrementY = 2;
        var len = list.length;
        for(var index = 0; index < len; index++)
        {
                var cube = new Cube();
                blockHolder.addChild(cube);
                cube.x = startX;
                cube.y = startY;

                startX += incrementX;
                startY += incrementY;

        }
}

// this function runs when we click our button
function onAdd(event)
{
        list.addItem("bleh");
}

 

If you click a few times, you’ll get some stair-stepping cubes.

Notice every time we add an item to the array, the “onListChanged” function is called. This function will fire any time the addItem method, or any method that modifies data in the list, is called. Now, not only do we draw to the list based off of dynamic data, we can also dynamically draw later if the data later changes. Let’s add one more addition and color the cubes using the data. We’ll add a new function to get a random color:

function getRandomColor()
{
        return Math.floor(Math.random() * 0xFFFFFF);
}

 

We’ll then modify our button’s click function to add a new random color:

// this function runs when we click our button
function onAdd(event)
{
        list.addItem(getRandomColor());
}

 

And finally add code to our loop to color the cubes from the data:

var colorTransform:ColorTransform = new ColorTransform();
colorTransform.alphaMultiplier = .5;
colorTransform.color = list.getItemAt(index) as uint;
cube.transform.colorTransform = colorTransform;

 

…which results in rave cubes:

list-tut-09

As you can see, this illustrates the flaw of our drawing algorithm. While it’s great it’s dynamic, we are destroying and redrawing the entire list everytime the data is changed, even if just 1 number is added. While it runs great because AS3 is pimp, this won’t scale to a lot of items, nor with a lot of other code going on. The Flash DataProvider class and the Flex ArrayCollection class both offer details about what changed, and where in the change event they dispatch. We can use that information to redraw more efficiently. Let’s move our redraw function to only draw the first time we launch, and use the changeType property of the DataProvider’s data change event. Here’s our modified code:

import flash.display.Sprite;
import flash.geom.ColorTransform;
import fl.data.DataProvider;
import fl.controls.Button;

stage.scaleMode = "noScale";
stage.align = "TL";

// create our list
var list:DataProvider = new DataProvider();
list.addItem(getRandomColor());
list.addItem(getRandomColor());
list.addItem(getRandomColor());
list.addItem(getRandomColor());
list.addItem(getRandomColor());
list.addItem(getRandomColor());
list.addItem(getRandomColor());
list.addEventListener("dataChange", onListChanged);

// create a sprite to hold our cubes
var blockHolder = new Sprite();
addChild(blockHolder);
blockHolder.x = 40;
blockHolder.y = 40;

// create a button to allow us to add things on a whim
var button = new Button();
addChild(button);
button.label = "Add";
button.addEventListener("click", onChange3rdColor);
button.x = 20;
button.y = 20;
button.width = 40;

makeT3hCubes();

function getRandomColor()
{
        return Math.floor(Math.random() * 0xFFFFFF);
}

function makeT3hCubes()
{
        blockHolder = new Sprite();
        addChild(blockHolder);
        
        var startX = 40;
        var startY = 60;
        var incrementX = 16;
        var incrementY = 2;
        var len = list.length;
        
        for(var index = 0; index < len; index++)
        {
                var cube = new Cube();
                blockHolder.addChild(cube);
                cube.x = startX;
                cube.y = startY;
                var colorTransform:ColorTransform = new ColorTransform();
                colorTransform.alphaMultiplier = .5;
                colorTransform.color = list.getItemAt(index) as uint;
                cube.transform.colorTransform = colorTransform;
                startX += incrementX;
                startY += incrementY;
        }
}

// when we add data to our list, this function runs
function onListChanged(event)
{
        if(event.changeType == "replace")
        {
                var cube = blockHolder.getChildAt(event.startIndex);
                var colorTransform:ColorTransform = new ColorTransform();
                colorTransform.alphaMultiplier = .5;
                colorTransform.color = event.items[0] as uint;
                cube.transform.colorTransform = colorTransform;
        }
}

// this function runs when we click our button
function onChange3rdColor(event)
{
        list.replaceItemAt(getRandomColor(), 2);
}

 

You can ignore all of the above; just look at the newly modified “onListChanged” function. Notice how it only redraws if the change is a replace. The redraw in this case is merely getting an existing cube, and changing it’s color with the updated data. This is insanely faster, and uses less RAM than destroying and re-creating a bunch of objects each time you redraw. Click the Add button below really quickly while you listen to this.

Invalidation

The issue with the above is that the event is synchronous. Both Flash and Flex will immediately dispatch a change event. While this is good to ensure that others have a change to know about data changes, or a GUI that draws on a single thread… not so much. Flash and Flex have solved this in their GUI controls using invalidation. The short version (vs. t3h long ones) is invalidation ensures you only redraw once per frame. The screen will only refresh once per frame in Flash Player, so there is no point to draw more than once per frame. The new issue is then how do you know what has actually changed? Do you draw immediately to ensure you only draw the changes yet risk drawing duplicate changes… or do you wait a frame and redraw everything? You have 3 choices, all of which are valid solutions.

The first is what I just said; wait a frame and refresh or redraw everything. Redrawing doesn’t imply that you simple remove all Sprites and listeners, and then build everything up again. You could utilize Object Pooling to re-use assets, and only draw the changes, such as simply setting new x and y values on an existing object, and hiding the ones you don’t need. This way, you’re only redrawing once per frame, regardless of how many data changes there were, and you can ensure you’re always displaying the most up to date data efficiently and as quickly as you reasonably can. Refreshing refers to when to the data itself changing. In Flash using DataProvider, you usually have to do this manually, as my above example shows using “replaceItemAt”. In Flex, you can utilize DataBinding and the ArrayCollection will dispatch a change event for that particular item. Either way, if you’re data itself is changing, and not new items being added to the list for example, and you just set the data on the items themselves without redrawing anyting. Here’s some modified code of the above that adds new colors, and refreshes the data instead of redrawing everything.

function onChange3rdColor(event)
{
        var len = list.length;
        for(var i = 0; i < len; i++)
        {
                list.replaceItemAt(getRandomColor(), i);
        }
        refresh();
}

function refresh()
{
        var len = list.length;
        for(var i = 0; i < len; i++)
        {
                var cube = blockHolder.getChildAt(i);
                var colorTransform:ColorTransform = new ColorTransform();
                colorTransform.alphaMultiplier = .5;
                colorTransform.color = list.getItemAt(i) as uint;
                cube.transform.colorTransform = colorTransform;
        }
}

 

The 2nd is to assume that the component itself will invalidate itself. In the previous example where I had the “onListChanged” function react to the changeType of replace. You could encapsulate that logic of changing colors into the Cube class itself. That way, if you set his cube.cubeColor variable 50 times, he’d only change it once per frame. This delegates responsibility of invalidation, making the component easier to work with. This doesn’t scale into the thousands of items, though, depending on what you’re doing.

The 3rd way is a more efficient way to redraw everything, which leads me to the next section…

Drawing Tons of Stuff

In a previous blog entry, I talked about drawing really large Array’s. Let’s show an example of that. Let’s draw over 9000!!?!?!11 cubes.

The following code create’s a bitmap that we then copy a cube bitmap onto. We then move where we copy the next one slightly, and repeat. Now, usually you could do this using a for loop, and increment the x and y position each iteration. That won’t work for the amount of cubes equal to Goku’s power level, though. So, we do 2 things. Instead of using a for loop, we keep track of which item we’re drawing on, called “currentIndex”. We draw something and then increment this variable, just like a for loop increments an index. Secondly, after drawing we keep track of how much time it took. If it takes too long, we take a break, aka, wait a frame. This gives Flash Player time to breathe and refresh the screen… and you’re code won’t crash (aka, ScriptTimeoutError). The time check we do is to ensure we don’t go over a certain amount of time. This can “sometimes” be the stage.frameRate. So, if you are at running at 30 frames per second, if you take longer than 33 milliseconds, you are cutting into your redraw time. Naturally, this isn’t exact as my aforementioned articles mention; Flash Player is doing a ton of other stuff than just waiting for you to draw stuff with your code each frame. I usually just do 5 to 10. The second thing we do is ensure we aren’t calling the same function more than 254 times. The default maximum amount of times you can have a function call itself (recrusion) is 255 (by default, you can change this value).

Alternate Pastebin view.

import flash.display.*;
import flash.geom.ColorTransform;
import flash.utils.getTimer;
import fl.controls.*;

stage.scaleMode = "noScale";
stage.align = "TL";

// create our list
var massive:Array = [];

var statusLabel = new Label();
addChild(statusLabel);
statusLabel.text = "Building Data...";
statusLabel.y = 22;

var button = new Button();
addChild(button);
button.label = "GO!";
button.addEventListener("click", onGOOOOO);
button.width = 40;

// create a bitmap to hold the cubes
var blocksBitmapData = new BitmapData(300, 200, true, 0);
var blockHolder = new Bitmap(blocksBitmapData);
addChild(blockHolder);
blockHolder.x = 40;
blockHolder.y = 40;

// initialize our Cube that'll we'll be copying from
var cube = new Cube();
var colorTransform = new ColorTransform();
colorTransform.alphaMultiplier = .5;
var cubeBitmapData = new BitmapData(cube.width, cube.height, true, 0);
var sourceRect = new Rectangle(0, 0, cube.width, cube.height);
var destPoint = new Point();

// initialize some variables
const MAX 					= 9999;
const TIMEOUT 				= 5;
const MAX_ITERATIONS 		= 254;
const RIGHT_BORDER 			= 300;
const BOTTOM_BORDER 		= 200;
const INCREMENT_X			= 4;
const INCREMENT_Y			= 4;
var worker 					= new Sprite();
var currentIndex 			= -1;
var startX 					= 0;
var startY					= 0;

// when you click da button, this party gets started
function onGOOOOO(event)
{
        button.enabled = false;
        button.label = "-";
        
        currentIndex = -1;
        
        // first, create our data
        makeTonsOfData();
        
        // second, start drawing
        drawNext();
}



function makeTonsOfData()
{
        for(var i = 0; i < MAX; i++)
        {
                massive[i] = getRandomColor();
        }
}

function getRandomColor()
{
        return Math.floor(Math.random() * 0xFFFFFF);
}

// increment our currentIndex
function drawNext(iteration:uint=0, totalTimeTaken:int=0)
{
        if(currentIndex + 1 < MAX)
        {
                currentIndex++;
                drawCube(iteration, totalTimeTaken);
        }
        else
        {
                statusLabel.text = "Done!";
                button.enabled = true;
                button.label = "GO!";
        }
}

// draw the current cube
function drawCube(iteration:uint=0, totalTimeTaken:int=0)
{
        var startTime = getTimer() - totalTimeTaken;
        colorTransform.color = massive[currentIndex] as uint;
        cubeBitmapData.draw(cube, new Matrix(), colorTransform);
        blocksBitmapData.copyPixels(cubeBitmapData, sourceRect, new Point(startX, startY), cubeBitmapData, destPoint, true);
        if(startX + INCREMENT_X < RIGHT_BORDER)
        {
                startX += INCREMENT_X;
        }
        else
        {
                startX = 0;
                if(startY + INCREMENT_Y < BOTTOM_BORDER)
                {
                        startY += INCREMENT_Y;
                }
                else
                {
                        startY = 0;
                }
        }
        statusLabel.text = currentIndex + " of " + MAX + ", " + Math.floor((currentIndex / MAX) * 100) + "%";
        if(needToChill(iteration, startTime))
        {
                smokeEmIfYaGotEm();
        }
        else
        {
                drawNext(iteration + 1, getTimer() - startTime);
        }
}

function needToChill(iteration, startTime):Boolean
{
        if(getTimer() - startTime > TIMEOUT || iteration >= MAX_ITERATIONS)
        {
                return true;
        }
        else
        {
                return false;
        }
}

// wait a frame
function smokeEmIfYaGotEm()
{
        worker.addEventListener(Event.ENTER_FRAME, onBreaksOverSucka);
}

function onBreaksOverSucka(event)
{
        worker.removeEventListener(Event.ENTER_FRAME, onBreaksOverSucka);
        drawNext();
}

Using a combination of the above inside a component allows it to redraw efficiently no matter how many times the Array/DataProvider/ArrayCollection changes. You can ensure that you’re redraw process won’t overly tax your Flash movie, and that it’ll scale to how ever many data points you can throw at it.

Effective Use of Change Events

…however, redrawing everything just because 1 data point in a million changed is just being really efficient at being dumb (or hitting an insane deadline, hehe). There are a few hybrid approaches you can take. The first is, if you know that your Array updates are going to be infrequent, you can redraw everything when you first have your Array fed to your component. Thenceforth, any time the data in that array changes, you just do a quick redraw. The second approach is re-use the same drawing algorithms in the Array change function, and just delay using them a frame, that way, you only actually draw the most recent changes once per frame.

Here is an example of the pragmatic first approach. The user clicks on a game sprite which changes its internal state. Since the user can click only so fast on one particular, we know we won’t be drawing too much per frame. While we could handle the clicks more efficiently, it’d be overkill, hence being pragmatic. Thus, if I add data to the Array, I draw a game sprite. If the data changes, I change the game sprite’s state. In this case, I make the MovieClip play an animation.

The code below shows how I add a listener to the DataProvider first, and then add data to it. This’ll trigger the change function to draw a Gambit clone for every item that is added. When you click, I figure out which one you clicked on, modify it’s data, and let the change function make the Gambit clone strike. In short, a very simple Model View Controller (MVC) example as well. The Model is our DataProvider, the View is the Gambit sprite(s), and the Controller is our root code.

import fl.data.*;
import fl.events.*;

var startX 			= 40;
var startY 			= 10;
var margin			= 10;

// make the data; our change handler will make Gambit clones
var gambits 		= new DataProvider();
gambits.addEventListener("dataChange", onDataChanged);
gambits.addItem({label: "GambitClone", data: "ready"});
gambits.addItem({label: "GambitClone", data: "ready"});
gambits.addItem({label: "GambitClone", data: "ready"});

function onDataChanged(event)
{
        var gambit;
        switch(event.changeType)
        {
                case DataChangeType.ADD:
                gambit 					= new Gambit();
                gambit.mouseChildren 	= false;
                gambit.mouseEnabled 	= true;
                addChildAt(gambit, event.startIndex);
                gambit.addEventListener("click", onClick);
                gambit.x 				= startX;
                gambit.y 				= (startY + gambit.height + margin) * event.startIndex;
                break;
                
                case DataChangeType.INVALIDATE:
                var item = event.items[0];
                if(item.data == "strike")
                {
                        item.data 	= "ready";
                        gambit 		= getChildAt(event.startIndex) as Gambit;
                        gambit.gotoAndPlay("strike");
                }
                break;
        }
}

// when we click, we change the data.  Our data change handler
// will deal with showing the correct state,
// in this case, making Gambit strike
function onClick(event)
{
        var gambit 	= event.currentTarget as Gambit;
        var index 	= getChildIndex(gambit);
        var item	= gambits.getItemAt(index);
        item.data 	= "strike";
        gambits.invalidateItemAt(index);
}

 

What if your data DOES change frequently, AND you’re drawing a lot of it? You use #2 like I mentioned above: draw over time, and utilize those drawing over time methods in your change handler once per frame. This is especially handy when drawing graphs and charts. Time to break out da Flex…

Real Time Graph

In the following example shows 3 bar graphs that draw camera events over time. I’ve included 3 graphs to tax your CPU. Simply click the “Start” button, and off you go. You can click the “Forward a Day” button while it’s drawing.

A camera event is an event that happens in a camera recording video, such as a security camera. For this example, this includes a start time of when the event occurred, when it ended (similar to Flash’s Camera object when the motion level drops below a threshold for a certain period of time), and how much motion was captured. Since there are tens of thousands of events captured on multiple cameras for just a day, this becomes challenging to draw. Worse, in my current project, I need to show a live feed of these guys as well as keeping the application responsive. This graph handles that by drawing over time in a given time slice. It uses Bitmap’s to draw as opposed to vector drawing via the Graphic object. While vector Graphics are visually more accurate to the using partial pixels, they don’t scale to this size; your app will eventually choke.

Key things to notice are I use invalidation to handle the setting of properties, including when new events are added later. Invalidation in Flex is somewhat similar to how it works in Flash (although Flash uses the broken stage.invalidate); you wait a frame before actually handling your changed data or drawing changes in the data. In Flex, you set a dirty flag, basically a Boolean, to true, and call invalidateProperties. This’ll make your commitProperties function run 1 frame later, no matter how many times you call invalidateProperties. There, you can handle the changes in properties in any order you like. This makes it easier for those using your component; they can set properties in any order, and know that you’ll “handle it”. Additionally, I handle the data changing later via the same invalidation mechanism; it just adds bars instead of redrawing everything using the same drawing function.

Naturally, there are 10 billion improvements that could be made (dynamically creating bitmaps to support larger charts, faster code loops, etc) but this is merely example code used to show you how you can draw extremely large lists in real time.

Realtime Chart Example – See It | Source View | Download

Conclusions

As you can see, drawing lists is a core function in how you create a lot of dynamic Flash content. Flash and Flex both give you classes that wrap Array’s to make it easier to build components of your own on top of them. Additionally, now that you know how they work, you have a better understanding of how the Flash and Flex List & DataGrid controls work. Finally, you’ve hopefully seen how you should be pragmmatic when drawing. While both DataProvider and ArrayCollection provide many notifications on what’s changed, most data usually changes in a predictable pattern. Therefore, using just invalidation, you can draw some pretty efficient, yet dynamic lists.

9 Replies to “Creating Dynamic Lists in Flex & Flash”

  1. hi Jesse,

    great practical examples of deferring code execution using invalidation and your needToChill approach… the elastic race track in action!

    thanks for sharing

  2. hey,

    great article. liked your method names, especially needToChill(), smokeEmIfYaGotEm() and onBreaksOverSucka() ;). would be the perfect flash redrawing guide if you also mentioned just rendering PARTS of a dataset. in your example you use mainly bitmap data…often however, you need to render movieclips and sprits. although you could possbily render a lot of them with delayed rendering over time and invalidation, it wouldn’t be a good idea because of ram, cpu etc. – so only rendering what’s currently visible in the UI would be better (like most flash/flex list based components actually work)…

  3. Thanks!

    If you look at the cubes example, I only redraw the 3rd cube; thus the only 1 part of the data set changed. The same goes for the Gambit example; I only play the MovieClip that was actually clicked on.

    Yes, there are 10 billion things I could add; like ensuring only things within a scrollRect are actually changed, only those actually in the DisplayList, only those with includeLayout as true in Flex stuff… the list goes on and on. There are so many optimizations, and architectural designs that can be used. …however, they are beyond the scope of this article; it’s already long winded enough.

  4. i was acutally thinking of scrolling through large data sets and only rendering the visible parts (for those who like to create their own list components, for example). yeah, took some time to read the article…you could write renderingmania part 2 one day ;). good stuff though!

Comments are closed.