Mobile ItemRenderer in ActionScript (Part 1)
If you are starting to do mobile development and are used to create all your ItemRenderers in MXML, you may notice that the small devices like phones and tablets do not perform as well as the desktop does and you need to start looking closely to different ways to optimize you app.
One of the ways to optimize your mobile app is to create your ItemRenderers in ActionScript. Narciso Jaramillo wrote a good article for Devnet with great tips for mobile development and one of the items was exactly that, to keep your ItemRenderers in pure ActionScript.
Narciso mentions the Flex Framework comes with two ItemRenderers one is LabelItemRenderer that extends UIComponent and the other is IconItemRenderer that extends LabelItemRenderer. Those classes are great, but sometimes you have a different use case that needs a different set of classes. So in my examples I will not use those renderers. Instead, I will extend from UIComponent directly or from SpriteVisualElement for an even more lightweight class than UIComponent.
For the purpose of this tutorial, I'm planning a series of posts explaining the basics and moving on to more difficult renderers.
Basic Example
Let's start with the most simple renderer, a TextField that displays some text. To make it simple, we will extend from UIComponent and implement the IDataRenderer interface required from the List.
But before talking about the renderer, let's talk about the other elements.
Application
You need to create a Mobile project. In this example, I use the simplest of all root containers, that is an Application, not the TabbedApplication nor ViewBasedApplication subclasses. I added a List that expands 100% as the only element.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark">
<fx:Script>
<![CDATA[
import mx.collections.ArrayList;
[Bindable]
private var items:ArrayList = new ArrayList( Font.enumerateFonts( true ) );
]]>
</fx:Script>
<fx:Style source="styles/Main.css"/>
<s:List id="list" width="100%"
height="100%"
dataProvider="{ items }"
labelField="fontName"
itemRenderer="renderers.UILabel"/>
</s:Application>
Styles
I added an external StyleSheet where I have all the styles for the ItemRenderer:
@namespace s "library://ns.adobe.com/flex/spark";
@namespace renderers "renderers.*";
renderers|UILabel
{
fontSize: 20;
color: #000000;
font-family: "_sans";
min-height: 50;
padding-left: 10;
}
ItemRenderer
I have a simple class extending UIComponet that gives me a few handy methods and the ability to participate in the CSS framework.
The 3 methods from UIComponent that we use are the following:
- measure where we read the min size of the renderer from the style sheet. That comes in handy because devices with different dpi resolutions need different sizes.
- createChildren where we create the TextField, set the styles and if there is the data already available, we set the text in the TextField to be displayed.
- updateDisplayList where we layout the elements and we draw a line separator for each ItemRenderer.
In additions to those methods, we have the data property, which is the implementation of the IDataRenderer interface. That interface is the contract with the List and it is the way that the data is pushed to the ItemRenderer. If the TextField exists at the moment when the data is set, we set the text on the TextField, otherwise, we save it for later.
package renderers
{
import flash.text.Font;
import flash.text.TextField;
import flash.text.TextFormat;
import mx.core.IDataRenderer;
import mx.core.UIComponent;
public class UILabel extends UIComponent implements IDataRenderer
{
//Protected properties
protected var labelField:TextField;
// Public Setters and Getters
protected var _data:Object;
public function set data( value:Object ):void
{
_data = value;
// if the textfield has been created we set the text
if( labelField )
{
labelField.text = Font( data ).fontName;
}
}
public function get data( ):Object
{
return _data;
}
// Contructor
public function UILabel()
{
percentWidth = 100;
}
// Override Protected Methods
override protected function measure():void
{
measuredHeight = measuredMinHeight = getStyle( "minHeight" );
}
//--------------------------------------------------------------------------
override protected function createChildren():void
{
labelField = new TextField();
labelField.defaultTextFormat = new TextFormat( getStyle( "fontFamily" ), getStyle( "fontSize" ) );
labelField.autoSize = "left";
addChild( labelField );
// if the data is not null we set the text
if( data )
labelField.text = Font( data ).fontName;
}
//--------------------------------------------------------------------------
override protected function updateDisplayList( unscaledWidth:Number, unscaledHeight:Number ):void
{
// position the field
labelField.x = getStyle( "paddingLeft" );
labelField.y = (unscaledHeight - labelField.textHeight ) / 2;
// we draw a separator line between each item
var lineY:int = unscaledHeight -1;
graphics.clear();
graphics.lineStyle( 1 );
graphics.moveTo( 0, lineY );
graphics.lineTo( unscaledWidth, lineY );
}
}
}
As you can see writing an ItemRenderer in ActionScript is not as difficult as it sounds.
I'm planning to show other examples in the future with multiple elements, background, states, text manipulation, multiple columns and more.
Continue reading Part 2 of this series on Item Renderers
John Gag
Nahuel Foronda
Dan
Joan Llenas
Would it make sense to externalize the data setter logic within the commitProperties method?
In Flex desktop you have to take care of this by yourself as the framework sets data many times in the same frame, which can become a bottleneck as the app grows..
Cheers!
Nahuel Foronda
Thomas Burleson
Great article on mobile optimization. Thanks for the concrete sample. I would highly recommend reading "Metadata-Driven Invalidation" http://bit.ly/f8zGBG...
The [Invalidate("properties,displaylist")] would simplify your code even more. John and I use it extensively to wonderful advantages.
Nahuel Foronda
But for a desktop, I think that your approach is good.
John Yanarella
I am using DescribeTypeCache in InvalidationTracker's introspection logic, so I would expect any performance hit would be limited to the first instantiated renderer of a given Class type.
Nahuel Foronda
Yes, caching the DescribeType helps, but I would use it only in certain cases for my mobile renderers. Let me be clear, your solution is pretty neat and I love it.
But for mobile, I'm using the bare minimum, not even UIComponent. I will show that in my next post.
Thomas Burleson
John and I are considering how to enhance [Invalidate] to work with non-UIComponents; to make immediate [1-frame] calls to specific validation handlers.
But back to your concersn... I am not trying to be pedantic, but I am striving for clear understandings.
You do not recommend UIComponents for renderers because of the the validation lifecycle delays?
What specifically is it that concerns your regarding performance on renderers?
Nahuel Foronda
The bigger the rendered the better is to use the UIComponent. But sometimes it is a simple thing that you can do it with just a SpriteVisualElement. There is no black and white here :)
polyGeek
Vincent
Thank you for this example. Extending the UIComponent provide a leightweight ItemRender but doing this way, how can you access some usefull properties of the ItemRender like itemIndex or how can you be aware of the user interactions with the item ?
Nahuel Foronda
That base class implemented the IItemRenderer interface that provides the itemIndex and other useful methods.
akin
Eric