Click here to Skip to main content
15,891,204 members
Articles / Programming Languages / XML

Using QNX Image and ImageCache in List Custom CellRenderer

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
14 Apr 2011CPOL4 min read 10.7K   2  
How to create a custom cell renderer skin for the QNX UI component List class

Introduction

The custom cell renderer will display an image, text, and box beneath the text for effect. 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 than 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

Custom-CellRenderer/AdobeMAX.png

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.

C#
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.

C#
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.

C#
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:

C#
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 outside this class to be used
     *  in more than 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 property 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 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.

History

  • 14th April, 2011: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --