为适应频繁更新数据优化Flex DataGrid

本人原创翻译,翻译来源:http://blogs.adobe.com/tomsugden/2010/04/optimizing_the_flex_datagrid_f.html#more

 

  当数据源发生变动时,DataGird默认会完全重画自己,在有些情况比如条目的渲染器可能需要根据需要扩大或缩小的时候,这是很有意义的,但是在其他情况下这就有问题了。如果数据源内容变化频繁,比如一个实时的价格表格,就可能出现过度渲染和增加CPU负担的问题。这篇博客介绍了一个简单的技巧,通过重写默认行为处理来更好的支持数据频繁更新降低CPU负担。

DefaultAndOptimizedDG.png

图1:红框是重绘区域,左边是默认表格,右边是经过完善的表格

你可以下载这个博客中使用的Flash Builder工程例子:

·FastDataGrid.zip

感谢Christophe Coenraets首先提供给我这个DataGrid的优化技巧。

 

首先了解一下DataGrid的默认的处理机制

比方说,你要呈现的数据提供了一个简单的可绑定的值对象,每一次数据对象的属性发生变化自动生成的绑定代码就会发出一个propertyChange事件,这个事件会被collection监听到并触发一个kind为更新的collectionChange事件。DataGrid内部和他的基础类ListBase会发生失效。

protected function collectionChangeHandler(event:Event):void
{
    ...
    itemsSizeChanged = true;

    invalidateDisplayList();
}

因此DataGrid就会在下一次更新的时候进行重绘。在最坏的情况下每一帧间隔都会有一条或多条数据源里的数据发生变化,这样显然会增加CPU负担。

 

重写默认的处理

DataGrid的默认处理方法可以被覆盖,所以可以优化成当数据只有某一项发生变化的时候该数据渲染器独立失效。优化通常分为两个步骤:

  1. 继承DataGrid覆写collectionChangeHandler()方法去忽略数据源collection中的数据被更新产生的collection变化事件。
  2. 写一个当检测到的数据项变化的时候,能够有自己的失效处理的自定义渲染器。

 

1.继承DataGrid

下面是对DataGrid的一个简单的扩展,重写了collectionChangeHandler()方法来忽略更新数据产生的kind为更新的collectionChange事件:

public class FastDataGrid extends DataGrid
{
    override protected function collectionChangeHandler(event:Event):void
    {
        if (event is CollectionEvent &&
            CollectionEvent(event).kind != CollectionEventKind.UPDATE)
        {
            super.collectionChangeHandler(event);
        }
    }
}

这个修改避免了在频繁更新数据的时候DataGrid的过度渲染,但是还需要一个经过修改知道怎么让自己失效的渲染器来完成这个功能。

注意:这个修改方法只适合数据源里的值对象派发propertyChange事件的情况,如果你使用自定义事件代替默认的事件,collection就会忽略这些导致DataGrid的失效不会被触发。

 

2.自失效的渲染器

下面的代码是一个自定义的ActionScript渲染器类,基于UIComponent,包含一个UITextField。这个渲染器监听到和他的数据项匹配的propertyChange事件的时候就会让自己失效。

public class PropertyChangeRenderer
    extends UIComponent 
    implements IDropInListItemRenderer, IListItemRenderer 
{
    private var textField:UITextField;
    private var column:DataGridColumn;
    private var updateText:Boolean;

    //-------------------------------
    //  listData
    //-------------------------------

    private var _listData:DataGridListData;

    [Bindable("dataChange")]
    public function get listData():BaseListData
    {
        return _listData;
    }

    public function set listData(value:BaseListData):void
    {
        _listData = value as DataGridListData;
        column = _listData 
            ? DataGrid(_listData.owner).columns[_listData.columnIndex] as DataGridColumn 
            : null;
    }

    //-------------------------------
    //  data
    //-------------------------------

    private var _data : Object;

    public function get data() : Object
    {
        return _data;
    }

    public function set data(value:Object):void
    {
        if (_data == value) return;

        if (_data && _data is IEventDispatcher)
        {
            IEventDispatcher(_data).removeEventListener(
                PropertyChangeEvent.PROPERTY_CHANGE,
                propertyChangeHandler);
        }

        _data = value;
        updateText = true;

        if (_data && _data is IEventDispatcher)
        {
            IEventDispatcher(_data).addEventListener(
                PropertyChangeEvent.PROPERTY_CHANGE,
                propertyChangeHandler,
                false, 0, true);
        }

        dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
    }

    private function propertyChangeHandler(event:PropertyChangeEvent):void
    {
        if (event.property == _listData.dataField)
        {
            updateText = true;
            invalidateProperties();
        }
    }

    //---------------------------------------------------------------------
    //
    //  Overrides : UIComponent
    //
    //---------------------------------------------------------------------

    override protected function createChildren():void
    {
        super.createChildren();

        textField = new UITextField();

        // hardcoded size and position to confine the redraw region, 
        // since it never changes in this example
        textField.width = 130;
        textField.height = 16;
        textField.x = 3;

        addChild(textField);
    }

    override protected function commitProperties():void
    {
        super.commitProperties();

        if (updateText)
        {
            updateText = false;
            textField.text = column.itemToLabel(data);
        }
    }
}

这个渲染器作出了些权衡取舍,不是一个通用的DataGridItemRenderer写法,他使用一个UIComponent来包含一个UITextField,而默认的DataGridItemRenderer只是简单的继承自UITextField,因此他的创建时间会更长。然而在运行的时候只有130x16大小的区域会重绘而不是整个表格,CPU负担会减少。

 

例子程序

FastDataGrid.zip工程包含一个应用,FastDataGridExample.mxml,演示了默认的和优化过的表格。你可以以这个表格和渲染器为基础进行改进,使其变得更完美或者更具有通用性。工程包含两个渲染器实现,注释中描述了他们的不同。

 

结论

如果你的应用里有频繁更新数据的表格,用Flash Debug Player特有的的显示重绘区域功能来确保没有过度重绘。如果有,考虑优化表格和渲染器来最少的占用CPU。这篇博客中的技巧同样也可以应用到AdvancedDataGrid当中。


Posted by tsugden at April 16, 2010 10:16 AM

 

posted @ 2013-01-08 10:39  小小有  阅读(863)  评论(0编辑  收藏  举报