Using QNX Image and ImageCache in List Custom CellRenderer

Posted on January 14, 2011 | 3 comments

In this blog I’ll show how to create a custom cell renderer skin for the QNX UI component List class. The custom cell renderer will display an image, text, and box beneath the text for affect. The List class implements virtualization of the cell renderers and reuses instances of the objects that are displayed as you scroll up and down. This allows for memory optimization and speed gains by not creating and destroying a bunch of cell renderer instances. In the process of displaying images in virtualizated instances you end up changing the source each time the data is assigned to the cell renderer instance.

This causes a problem of performance and visual quirks. For example if updating the image url takes longer then the time it takes to have the data value changed with a new value (try flicking a long list really fast, the instances of the cell renderer will be set a bunch of times before it slows down). This situation might cause a flicker of the old image before the new image loads. Luckily QNX classes probably a mechanism to help with this situation.

By caching the loaded images after the first load you can quickly load image data before any new data is set in a cell renderer. I will now describe in detail how this works with some code. All of the code below can be found on github at https://github.com/renaun/QNXListImageCache.

Example Application Screenshot

Getting Data

This example application pulls down the Flickr image feed for the Adobe MAX flickr account. It parses the RSS feed in rss2 format and creates a QNX DataProvider class with data objects holding the title and url to the image.

URLLoader

Use the URLLoader class to load the flickr feed and then listen for the Event.COMPLETE event to parse the data.

private function loadImageData():void
{
    var loader:URLLoader = new URLLoader();
    loader.addEventListener(Event.COMPLETE, onLoadData);
    // Loads a list of MAX images returned as json format
    loader.load(new URLRequest("http://api.flickr.com/services/feeds/photos_public.gne?id=43067655@N07&format=rss2"));
}

XML & DataProvider

The flickr feed was requested to be in the rss2 format. We use the flickr “media” specific namespace to pull of the title and content from the rss2 items.

protected function onLoadData(event:Event):void
{
    var xml:XML = new XML(event.target.data);
    var media:Namespace = new Namespace("http://search.yahoo.com/mrss/");
    var xmlList:XMLList = xml..item;
    var len:int = xmlList.length();
    for (var i:int = 0; i<len; i++)
    {
        var obj:Object = new Object();
        obj.text = xmlList[i].media::title+"";
        obj.url = xmlList[i].media::content.@url + "";
        dp.addItem({label:obj.text, data:obj});
    }
    // ... more code here
}

List & Label

Now we continue in the onLoadData function and create the List and Label for the application.

protected function onLoadData(event:Event):void
{
    // ... previous code here

    list = new List();
    list.setSkin(CacheImageCellRenderer);
    list.dataProvider = dp;
    list.width = stage.stageWidth;
    list.height = stage.stageHeight;
    list.columnWidth = 240;
    list.rowHeight = 240;
    list.x = 0;
    list.y = 60;
    list.addEventListener(ListEvent.ITEM_CLICKED, itemClickedHandler);
    addChild(list);
   
    var format:TextFormat = new TextFormat();
    format.size = 24;
    format.bold = true;
    format.color = 0xFFFFFF;
   
    lblSelected = new TextField();
    lblSelected.defaultTextFormat = format;
    lblSelected.x = 6;
    lblSelected.y = 6;
    lblSelected.text = "Selected an image!";
    addChild(lblSelected);
    layout();
}

Taking a look at the List class you will see list.setSkin(CacheImageCellRenderer); and list.dataProvider = dp;. Now any time the List class makes a new cell renderer object instance it will create a new com.renaun.renderer.CacheImageCellRenderer instance. As the List scrolls up and down each cell renderer instance will be assigned with data from the list.dataProvider which we set with the dp value.

com.renaun.renderer.CacheImageCellRenderer

The custom cell renderer in this example extends QNX’s CellRenderer and overrides the data setter function. The class prepares the Image, Label, and background graphic components in the constructor. It doesn’t set any parts of the components that depend on data values, that is left up to the updateCell() method which is called after a new data value is set. Here is the whole class, we’ll take a look at the parts below:

package com.renaun.renderer
{
import flash.display.Sprite;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;

import qnx.ui.display.Image;
import qnx.ui.listClasses.CellRenderer;
import qnx.ui.text.Label;
import qnx.utils.ImageCache;

public class CacheImageCellRenderer extends CellRenderer
{
    /**
     *  This is a static cache object. Alternatively you can
     *  create this object out side this class to be used
     *  in more then one place.
     */

    public static var cacheObject:ImageCache = new ImageCache();
   
    public function CacheImageCellRenderer()
    {
        super();
        initBackground();
    }
   
    /**
     *  
     */

    protected var img:Image;
    protected var lbl:Label;
    protected var bg:Sprite;
    protected var format:TextFormat;
   
    /**
     *  Updates the text and image everytime a new data is set
     *  for this renderer instance.
     */

    override public function set data(value:Object):void
    {
        super.data = value.data;
        //trace(value.data.text + " - " + value.data.url);
        updateCell();
    }
   
    /**
     *  Update the image and text if there is valid data.
     */

    private function updateCell():void
    {
        if (this.data)
        {
            //trace("loading: " + data.url);
            img.setImage(data.url);
            lbl.text = data.text;
        }      
    }
   
    /**
     *  Create all the cell renderer components just once
     */

    private function initBackground():void
    {
        img = new Image();
        lbl = new Label();
        bg = new Sprite();
       
        format = new TextFormat();
        format.color = 0xEEEEEE;
        format.size = 12;
        format.bold = true;
       
        bg.graphics.clear();
        bg.graphics.beginFill(0x000000, .7);
        bg.graphics.drawRect(0, 0, 240, 50);
        bg.graphics.endFill();
        img.setPosition(0,0);
        img.setSize(240,134);
        // Setting the cache propery on QNX Image class
        // takes care of adding and checking for images on
        // the ImageCache object.
        img.cache = CacheImageCellRenderer.cacheObject;
       
        lbl.width = 240;
        lbl.format = format;
        lbl.wordWrap = true;
        lbl.x = 5;
        lbl.y = 0;
        lbl.width = 220;
        lbl.autoSize = TextFieldAutoSize.LEFT;
       
        addChild(img);
        addChild(bg);
        addChild(lbl);
    }
}
}

updateCell()

Each time that a new data object is set to this instance it updates its appearance by setting the img.setImage(data.url); and lbl.text = data.text;.

ImageCache

The class has a static instance of ImageCache class called cacheObject. This static reference could have been outside of this class but for this example the main point is to have the same instance of the ImageCache class ready to assign to X number of Image class instances.

img.cache

For each instance of the custom cell renderer there is a QNX Image class created which will change based on the data.url value. By setting img.cache = CacheImageCellRenderer.cacheObject; we are telling the Image class to first check for the image in the CacheImageCellRenderer.cacheObject instance. If the image source, keyed by the url, is not found it will load the image data and then save it in the CacheImageCellRenderer.cacheObject instance. This makes subsequent or frequent calls to load image data in the Image class very fast as it pulls the cached BitmapData from CacheImageCellRenderer.cacheObject.

There isn’t really much to it, this is how to get great performance by using the ImageCache with your Image components in your custom List cell renderer

Where to go from here
The ImageCache will use more memory. You can limit the number of items the ImageCache class caches, get out the api here. You can also use one static reference to one instance of ImageCache across different components, depending on the content of an application this might make sense to do.

Another approach to bypass the initial loading times as the List first appears is to actual prefill the ImageCache instance with the initial images.

  • http://spy6.blogspot.com George

    Great post Renaun, how much memory would you dedicate to the ImageCache system so that it still runs smoothly on the Playbook ?!

    • http://www.renaun.com Renaun Erickson

      This depends on the size and number of the images. Also the it depends on the List and number of cell renderers and how large the lists might be. I would say generally the defaults will work just fine and then work to limit it based on content extremes (random large sized or large numbered of images being used).

  • Pingback: links for 2011-01-14 « Boskabout

  • Pingback: Extending QNX TileList: Liquid Tile List : Mihai Corlan

  • Pingback: Extending QNX TileList: Liquid Tile List (Adobe Flash Platform Blog)

  • Mahesh102006

    Your post are really usefull…..i am a big fan of your site….keep going and keep supporting us…