Flex Mobile - TileLayout性能问题

时间:2012-04-05 08:43:39

标签: flex list mobile tiles flex-mobile


我正在为平板电脑设备构建移动应用程序。
我的List组件与tile布局存在巨大的性能问题 我在屏幕上显示大约20-24个项目的页面,用户可以滚动页面。不幸的是,列表组件生成新页面的速度非常慢。
总体而言,我测试了带有平铺布局的独立列表,并且它在每种情况下的性能都非常慢(我在iPad1和iPad2上测试过它)。 如果您知道问题的解决方案,请建议。

感谢。

2 个答案:

答案 0 :(得分:3)

通常,瓷砖的弯曲非常慢。这是因为它允许每个项目渲染器的大小可变,而measure / updateDisplayList需要花费大量时间。

  1. 我建议您使用自定义(自制)磁贴列表,并在此情况下删除flex组件。如果您仅将其用于布局,则可以手动/编程计算拼贴中每个项目的位置并将其移动到右x / y像素。

  2. 还要注意效果!它们可以在AIR模拟器或桌面浏览器中正常工作,但在移动设备上占用大量资源。

  3. 如果您仍想使用flex磁贴组件,而不是创建自己的磁贴控件,那么我建议您使用渲染器修复内部显示的所有项目,并在TileList中指定columnWidth和rowHeight declatation。这可能会提高性能......:)

  4. 还提出一些示例代码并没有伤害。也许你正在做一些错误的事情......不知道:额外的失效,糟糕的渲染等......!

答案 1 :(得分:0)

已经有一段时间了,所以我不记得细节。这是我使用的代码,但请记住,我写的组件是出于特定目的,包括一些不良实践,以在很短的时间内完成特定的问题。但是我希望有用。

<强> pagedList

package components.pagedList
{
    import components.pagedList.vo.PageItemVo;      
    import mx.collections.ArrayCollection;
    import mx.collections.IList;
    import mx.core.IFactory;
    import mx.core.UIComponentGlobals;
    import mx.events.CollectionEvent;
    import mx.events.CollectionEventKind;

    import spark.components.List;
    import spark.layouts.HorizontalLayout;

/**
 * 
 */
public class PagedList extends List
{


    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------        

    public function PagedList()
    {
        super();
    }


    //--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------    

    /**
     * Dimentions for the inner items should be specifyed because 
     * this component needs to know how many items can be placed in 
     * one page, but don't have the ability to create this items.
     * Default value is 1 to avoid division by zero.
     */
    public var innerItemWidth:Number = 1;

    public var innerItemHeight:Number = 1;

    /**
     * When inner item dimentions are set the component calculates how many items 
     * (per row and col) can be shown on one scren and pass these data to the page 
     * trough the dataProvider for the page.
     */
    private var pageRowCount:int = 1;

    private var pageColCount:int = 1;   

    /**
     * Count of the items that can fit in one page.
     * This count is used to slice the data into picies
     * with the same length. 
     */
    private var itemsPerPage:int = 0;   

    /**
     * Original data provider is saved here so we can re-slice it when
     * size change occure and listen for data change event.
     * 
     * TODO: implement the data chage listener
     */
    private var _originalDataProvider:IList;

    /**
     * 
     */
    private var cachedUnscaledWidth:Number = 0;
    private var cachedUnscaledHeight:Number = 0;

    //----------------------------------
    //  Custom tiles data
    //----------------------------------

    private var _customTilesData:Array;

    private var customTilesDataChanged:Boolean;

    public function set customTilesData(value:Array):void
    {
        _customTilesData = value;
        customTilesDataChanged = true;
        invalidateProperties();
    }

    public function get customTilesData():Array
    {
        return _customTilesData;
    }

    //----------------------------------
    //  dataProvider
    //----------------------------------

    private var originalDataProviderChanged:Boolean;

    [Inspectable(category="Data")]  
    /**
     * 
     */
    override public function set dataProvider(value:IList):void
    {
        if (_originalDataProvider)
            _originalDataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, originalDataProvider_collectionChangeHandler);     

        originalDataProviderChanged = true;

        if (value)
            value.addEventListener(CollectionEvent.COLLECTION_CHANGE, originalDataProvider_collectionChangeHandler, false, 0, true);

        _originalDataProvider = value;
        updatePagedData();
    }   

    /**
     * 
     */
    private function originalDataProvider_collectionChangeHandler(event:CollectionEvent):void
    {
        //trace('data changed:', event.kind, 'location:', event.location);
        if (event.kind == CollectionEventKind.REPLACE)
        {
            updateReplacedItem(event.location);
        }

    }   

    /**
     * 
     */
    private function updateReplacedItem(index:int):void
    {
        if (dataProvider)
        {
            var pageNumber:int = int(index / itemsPerPage);
            var itemIndex:int = index - (pageNumber * itemsPerPage);
            if (dataProvider[pageNumber])
            {
                var pageData:PageItemVo = PageItemVo(dataProvider[pageNumber])
                pageData.data[itemIndex] = _originalDataProvider[index];
            }
        }
    }   

    //----------------------------------
    //  innerItemRenderer
    //----------------------------------

    private var _innerItemRenderer:IFactory;

    private var innerItemRendererChanged:Boolean;

    public function set innerItemRenderer(value:IFactory):void
    {
        _innerItemRenderer = value;
        innerItemRendererChanged = true;
        invalidateProperties();
    }

    public function get innerItemRenderer():IFactory
    {
        return _innerItemRenderer;
    }


    //----------------------------------
    //  gaps
    //----------------------------------    

    /**
     * 
     */
    public function set verticalGap(value:Number):void
    {
        _verticalGap = value;
        gapsChanged = true;
        invalidateProperties();
    }

    public function get verticalGap():Number
    {
        return _verticalGap;
    }

    /**
     * 
     */
    public function set horizontalGap(value:Number):void
    {
        _horizontalGap = value;
        gapsChanged = true;
        invalidateProperties();
    }

    public function get horizontalGap():Number
    {
        return _horizontalGap;
    }

    private var _verticalGap:Number;

    private var _horizontalGap:Number;

    private var gapsChanged:Boolean;

    //--------------------------------------------------------------------------
    //
    //  Overridden methods
    //
    //--------------------------------------------------------------------------

    protected function updatePagedData():void
    {
        if (_originalDataProvider)
        {
            var pagedData:IList = createPagedData(_originalDataProvider);
            super.dataProvider = pagedData;
            invalidateProperties();
        }
    }

    private function createPagedData(value:IList):IList
    {
        var nestedData:Array = [];
        var dataList:Array = value.toArray();
        var pageData:PageItemVo;
        if (itemsPerPage)
        {
            var customTilesCount:int = customTilesData ? customTilesData.length : 0;
            var normalItemsPerPage:int = itemsPerPage - customTilesCount;
            while (dataList.length)
            {
                pageData = new PageItemVo();
                var data:Array = dataList.splice(0, normalItemsPerPage);
                for (var i:int = 0 ; i < customTilesCount ; i++)
                {
                    data.push( customTilesData[i] );
                }
                pageData.data = new ArrayCollection( data );
                pageData.colsCount = pageColCount;
                pageData.rowsCount = pageRowCount;
                pageData.itemWidth = innerItemWidth
                pageData.itemHeight = innerItemHeight;
                pageData.horizontalGap = horizontalGap;
                pageData.verticalGap = verticalGap;
                pageData.innerItemRenderer = _innerItemRenderer;
                nestedData.push(pageData);
            }
        }
        return new ArrayCollection(nestedData);     
    }   

    //----------------------------------
    //  Component lifecycle
    //----------------------------------    

    override protected function commitProperties():void
    {
        if (gapsChanged || innerItemRendererChanged || customTilesDataChanged)
        {
            updatePagedData();
            gapsChanged = false;
            innerItemRendererChanged = false;
            customTilesDataChanged = false;
        }       
    }

    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {
        super.updateDisplayList(unscaledWidth, unscaledHeight);
        caluclateItemsPerPage(unscaledWidth, unscaledHeight);
        // We have to update the dataProvider so it can re-slice the pages 
        // in case of orientation rotation or some other resize
        if (_originalDataProvider)
            if ( cachedUnscaledWidth != unscaledWidth || cachedUnscaledHeight != unscaledHeight )
                dataProvider = _originalDataProvider;

        cachedUnscaledWidth = unscaledWidth;
        cachedUnscaledHeight = unscaledHeight;
    }

    protected function caluclateItemsPerPage(unscaledWidth:Number, unscaledHeight:Number):void
    {
        var myLayout:HorizontalLayout = HorizontalLayout(layout);
        var horizontalPaddings:Number = myLayout.paddingLeft + myLayout.paddingRight;
        var verticalPaddings:Number = myLayout.paddingTop + myLayout.paddingRight;
        pageRowCount = (unscaledHeight - verticalPaddings) / (innerItemHeight + verticalGap);
        pageColCount = (unscaledWidth - horizontalPaddings) / (innerItemWidth + horizontalGap);
        itemsPerPage = pageRowCount * pageColCount; 
    }   


}
}

PageItemRenderer

package skins.pagedList
{
    import components.pagedList.vo.PageItemVo;

    import flash.display.DisplayObject;

    import mx.collections.ArrayCollection;
    import mx.core.IDataRenderer;
    import mx.core.IFactory;
    import mx.core.UIComponent;
    import mx.events.FlexEvent;

    import spark.components.IItemRenderer;



//--------------------------------------
//  Events
//--------------------------------------

/**
 *  Dispatched when the <code>data</code> property changes.
 *
 *  <p>When you use a component as an item renderer,
 *  the <code>data</code> property contains the data to display.
 *  You can listen for this event and update the component
 *  when the <code>data</code> property changes.</p>
 * 
 *  @eventType mx.events.FlexEvent.DATA_CHANGE
 */
[Event(name="dataChange", type="mx.events.FlexEvent")]  





/**
 * 
 * ASDoc comments for this item renderer class
 * 
 */
public class PageItemRenderer extends UIComponent
    implements IDataRenderer, IItemRenderer
{





    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------        

    public function PageItemRenderer()
    {
        super();
        cacheAsBitmap = true;
    }   



    //--------------------------------------------------------------------------
    //
    //  Variables 
    //
    //--------------------------------------------------------------------------

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

    /**
     *  @private
     */
    private var _data:PageItemVo;

    private var dataChanged:Boolean;

    [Bindable("dataChange")]

    /**
     *  The implementation of the <code>data</code> property
     *  as defined by the IDataRenderer interface.
     *  When set, it stores the value and invalidates the component 
     *  to trigger a relayout of the component.
     */
    public function get data():Object
    {
        return _data;
    }

    /**
     *  @private
     */
    public function set data(value:Object):void
    {
        _data = PageItemVo(value);
        colCount = _data.colsCount;
        rowCount = _data.rowsCount;
        itemWidth = _data.itemWidth;
        itemHeight = _data.itemHeight;
        horizontalGap = _data.horizontalGap;
        verticalGap = _data.verticalGap;
        innerItemRenderer = _data.innerItemRenderer;
        _tilesData = ArrayCollection(_data.data);

        dataChanged = true;

        if (hasEventListener(FlexEvent.DATA_CHANGE))
            dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));

        invalidateProperties();
    }   

    //----------------------------------
    //  gaps
    //----------------------------------    

    /**
     * 
     */
    public function set verticalGap(value:Number):void
    {
        _verticalGap = value;
        gapsChanged = true;
        invalidateProperties();
    }

    public function get verticalGap():Number
    {
        return _verticalGap;
    }

    /**
     * 
     */
    public function set horizontalGap(value:Number):void
    {
        _horizontalGap = value;
        gapsChanged = true;
        invalidateProperties();
    }

    public function get horizontalGap():Number
    {
        return _horizontalGap;
    }   

    private var _verticalGap:Number = 5;    

    private var _horizontalGap:Number = 5;

    private var gapsChanged:Boolean;


    //----------------------------------
    //  itemIndex
    //----------------------------------

    private var _itemIndex:int;

    public function get itemIndex():int
    {
        return _itemIndex;
    }

    public function set itemIndex(value:int):void
    {
        if (value == _itemIndex)
            return;

        invalidateDisplayList();
        _itemIndex = value;
    }       

    //----------------------------------
    //  showsCaret
    //----------------------------------

    private var _showsCaret:Boolean = false;

    public function get showsCaret():Boolean
    {
        return _showsCaret;
    }

    /**
     *  @private
     */    
    public function set showsCaret(value:Boolean):void
    {
        if (value == _showsCaret)
            return;

        _showsCaret = value;
        invalidateDisplayList();
    }


    //----------------------------------
    //  selected
    //----------------------------------

    private var _selected:Boolean = false;

    public function get selected():Boolean
    {
        return _selected;
    }

    public function set selected(value:Boolean):void
    {
        if (value == _selected)
            return;

        _selected = value; 
    }


    //----------------------------------
    //  dragging
    //----------------------------------

    private var _dragging:Boolean = false;

    public function get dragging():Boolean
    {
        return _dragging;
    }

    public function set dragging(value:Boolean):void
    {
        _dragging = value;
    }   

    //----------------------------------
    //  label
    //----------------------------------

    private var _label:String;

    public function get label():String
    {
        return _label;
    }

    /**
     *  @private  
     */
    public function set label(value:String):void
    {
        _label = value;
    }       

    //----------------------------------
    //  item properties
    //----------------------------------

    /**
     * Dimentions for the inner items should be specifyed because 
     * this component needs to know how many items can be placed in 
     * one page, but don't have the ability to create this items.
     * Default value is 1 to avoid division by zero.
     */
    public var itemWidth:Number = 1;

    public var itemHeight:Number = 1;

    /**
     * When inner item dimentions are set the component calculates how many items 
     * (per row and col) can be shown on one scren and pass these data to the page 
     * trough the dataProvider for the page.
     */
    public var rowCount:int = 1;

    public var colCount:int = 1;        

    private var _tilesData:ArrayCollection;

    private var sizeChanged:Boolean;

    private var _tileContainer:UIComponent;

    /**
     * 
     */
    private var innerItemRenderer:IFactory;

    //--------------------------------------------------------------------------
    //
    //  Overridden methods
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  Component lifecycle
    //----------------------------------        

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

        if (dataChanged)
        {
            dataChanged = false;
            createTiledContent();
        }
        if (gapsChanged)
        {
            createTiledContent();
        }
    }

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

        measuredMinHeight = measuredWidth =  parent.width;
        measuredMinHeight = measuredHeight = parent.height;
    }   

    override protected function updateDisplayList(unscaledWidth:Number,
                                                  unscaledHeight:Number):void
    {
        super.updateDisplayList(unscaledWidth, unscaledHeight);
        var tileCntWidth:Number = colCount * (itemWidth + horizontalGap);
        _tileContainer.x = 0.5 * (unscaledWidth - tileCntWidth);
    }






    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------    

    protected function createTiledContent():void
    {
        clearChildren();
        _tileContainer = new UIComponent();
        var itemsCount:int = _tilesData.length;
        var row:int, col:int, item:DisplayObject;
        for (var i:int = 0 ; i < itemsCount ; i++ )
        {
            row = int( i / colCount );
            col = i - row * colCount;
            if (_tilesData[i].hasOwnProperty("itemFactory"))
            {
                item = IFactory(_tilesData[i].itemFactory).newInstance();
            }
            else
            {
                item =  innerItemRenderer.newInstance();
            }
            Object(item).data = _tilesData[i];
            item.x = col * (itemWidth + horizontalGap);
            item.y = row * (itemHeight + verticalGap);
            _tileContainer.addChild(item);
        }
        addChild(_tileContainer);
        invalidateSize();
        invalidateDisplayList();
    }

    //--------------------------------------------------------------------------
    //
    //  Helper methods
    //
    //--------------------------------------------------------------------------    

    private function clearChildren():void
    {
        var numChildren:int = this.numChildren;
        for (var i:int = 0 ; i < numChildren ; i++ )
        {
            this.removeChildAt(0);
        }
    }       

}
}

<强> PagedItemVo

package components.pagedList.vo
{
    import flash.events.EventDispatcher;
    import flash.events.IEventDispatcher;

    import mx.collections.IList;
    import mx.core.IFactory;

    [Bindable]
    public class PageItemVo extends EventDispatcher
    {

        public var data:IList;

        public var rowsCount:int;

        public var colsCount:int;

        public var itemWidth:Number;

        public var itemHeight:Number;

        public var verticalGap:Number;

        public var horizontalGap:Number;

        public var innerItemRenderer:IFactory;

        public function PageItemVo(target:IEventDispatcher=null)
        {
            super(target);
        }
    }
}

<强> ScrollingListSkin

package skins.pagedList
{
    import flash.display.Graphics;
    import flash.display.Sprite;
    import flash.events.MouseEvent;

    import mx.core.DPIClassification;
    import mx.core.ScrollPolicy;
    import mx.events.FlexEvent;
    import mx.events.TouchInteractionEvent;

    import spark.events.IndexChangeEvent;
    import spark.events.RendererExistenceEvent;
    import spark.skins.mobile.ListSkin;
    import spark.skins.mobile.supportClasses.MobileSkin;

    public class ScrollingListSkin extends ListSkin
    {
        private var pageIndicator:Sprite;
        private var indicatorSize:uint;
        private var _isHorizontal:Boolean;
        private var _suspendPageIndicatorShortcut:Boolean;

        public function ScrollingListSkin()
        {
            super();

            switch (applicationDPI)
            {
                case DPIClassification.DPI_320:
                {
                    indicatorSize = 32;
                    break;
                }
                case DPIClassification.DPI_240:
                {
                    indicatorSize = 24;
                    break;
                }
                default:
                {
                    indicatorSize = 16;
                    break;
                }
            }
        }

        //--------------------------------------------------------------------------
        //
        //  Overridden methods
        //
        //--------------------------------------------------------------------------

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

            scroller.setStyle("skinClass", PagedListScrollerSkin);

            // page indicator
            pageIndicator = new Sprite();

            // TODO (jasonsj): extend pageIndicator hit area to use the entire 
            // width/height of the List as a shortcut. Currently this only works
            // in the tiny area where the indicators are.
            //pageIndicator.addEventListener(MouseEvent.MOUSE_DOWN, pageIndicaterMouseHandler);
            //pageIndicator.addEventListener(MouseEvent.MOUSE_UP, pageIndicaterMouseHandler);
            //pageIndicator.addEventListener(MouseEvent.MOUSE_MOVE, pageIndicaterMouseHandler);

            addChild(pageIndicator);

            // listen for changes to the list
            dataGroup.addEventListener(FlexEvent.UPDATE_COMPLETE, dataGroupUpdateComplete);
            scroller.addEventListener(TouchInteractionEvent.TOUCH_INTERACTION_START, touchInteractionStart);
            scroller.addEventListener(TouchInteractionEvent.TOUCH_INTERACTION_END, positionChanged);
        }

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

            // isHorizontal
            /*var hScrollPolicy:Boolean = getStyle("horizontalScrollPolicy") == ScrollPolicy.ON;
            var vScrollPolicy:Boolean = getStyle("verticalScrollPolicy") == ScrollPolicy.ON;
            _isHorizontal = hScrollPolicy && !vScrollPolicy;*/
            _isHorizontal = true;
        }

        override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
        {
            super.drawBackground(unscaledWidth, unscaledHeight);

            var pos:Number = (isHorizontal) ? scroller.viewport.horizontalScrollPosition :
                scroller.viewport.verticalScrollPosition;
            var viewportSize:Number = (isHorizontal) ? scroller.viewport.width :
                scroller.viewport.height;

            var selectedIndex:int = Math.round(pos / viewportSize);
            var numElements:int = dataGroup.numElements;

            var g:Graphics = pageIndicator.graphics;
            g.clear();

            // if we have only one page there shouldn't be any scrollbar visuals
            if (numElements == 1) return;

            var axisPos:Number = 0;
            var centerPos:Number = indicatorSize / 2;
            var radius:Number = indicatorSize / 4;
            //TODO: make so the color could be specifyed outisde 
            var selectionColor:Number = 0x000000; //getStyle("selectionColor");


            //var elementsPerPage:int = Math.floor(unscaledWidth/FileIconItemRenderer.ITEM_WIDTH) * Math.floor(unscaledHeight/FileIconItemRenderer.ITEM_HEIGHT);

            for (var i:uint = 0; i < numElements; i++)
            {
                if (i == selectedIndex)
                    g.beginFill(selectionColor, 1);
                else
                    g.beginFill(0, .25);

                if (isHorizontal)
                    g.drawCircle(axisPos + centerPos, centerPos, radius);
                else
                    g.drawCircle(centerPos, axisPos + centerPos, radius);

                g.endFill();

                axisPos += indicatorSize;
            }

            var pageIndicatorX:Number = (isHorizontal) ? (unscaledWidth - axisPos) / 2 :
                unscaledWidth - (indicatorSize * 1.5);
            var pageIndicatorY:Number = (isHorizontal) ? unscaledHeight - (indicatorSize * 1.5):
                (unscaledHeight - axisPos) / 2;

            setElementPosition(pageIndicator, Math.floor(pageIndicatorX), Math.floor(pageIndicatorY));
        }

        override public function styleChanged(styleProp:String):void
        {
            super.styleChanged(styleProp);

            var allStyles:Boolean = !styleProp || styleProp == "styleName";

            if (allStyles || styleProp == "horizontalScrollPolicy" ||
                styleProp == "verticalScrollPolicy")
            {
                invalidateProperties();
                invalidateDisplayList();
            }
        }

        private function get isHorizontal():Boolean
        {
            return _isHorizontal;
        }

        //--------------------------------------------------------------------------
        //
        //  Event Handlers
        //
        //--------------------------------------------------------------------------

        private function dataGroupUpdateComplete(event:FlexEvent):void
        {
            invalidateDisplayList();
        }

        private function touchInteractionStart(event:TouchInteractionEvent):void
        {
            _suspendPageIndicatorShortcut = true;
        }

        private function positionChanged(event:TouchInteractionEvent):void
        {
            invalidateDisplayList();
            _suspendPageIndicatorShortcut = false;
        }

        private function pageIndicaterMouseHandler(event:MouseEvent):void
        {
            event.preventDefault();

            if (_suspendPageIndicatorShortcut)
                return;

            // Mouse events on the pageIndicator sprite will jump to the selected page
            var pos:Number = (isHorizontal) ? event.localX : event.localY;
            var size:Number = (isHorizontal) ? pageIndicator.width : pageIndicator.height;

            pos = Math.min(Math.max(pos, 0), size) - (indicatorSize / 2);

            var viewportSize:Number = (isHorizontal) ? scroller.viewport.width : scroller.viewport.height;
            viewportSize = viewportSize * dataGroup.numElements;

            var viewportPosition:Number = (pos / size) * viewportSize;

            if (isHorizontal)
                scroller.viewport.horizontalScrollPosition = viewportPosition;
            else
                scroller.viewport.verticalScrollPosition = viewportPosition;
        }
    }
}

示例:

<pagedList:PagedList id="displayList" 
                 width="100%" height="100%"
                 itemRenderer="skins.pagedList.PageItemRenderer"
                 innerItemRenderer="components.pagedList.tiles.FolderTileItem"
                 innerItemWidth="146" innerItemHeight="150"
                 skinClass="skins.pagedList.ScrollingListSkin"
                 verticalScrollPolicy="off"
                 horizontalScrollPolicy="on"
                 pageScrollingEnabled="true"
                 horizontalGap="15" verticalGap="20"
                 contentBackgroundAlpha="0"
                 selectionColor="0xFFFFFF">
<pagedList:layout>
    <s:HorizontalLayout columnWidth="{displayList.width}" 
                        variableColumnWidth="false" 
                        gap="0"
                        paddingTop="30"/>
</pagedList:layout>

不幸的是,我无法与您共享FolderTileItem源,但它是一个简单的扩展Sprite的简单组件。