2012-04-05 3 views
1


Я создаю мобильное приложение для планшетных устройств.
У меня огромная проблема с производительностью с компонентом List с компоновкой плитки.
Я показываю страницы с 20-24 элементами на экране, и пользователь может прокручивать страницы. К сожалению, компонент списка создает новую страницу крайне медленно.
В целом, я тестировал автономный список с компоновкой плитки и ее производительность в каждом сценарии очень медленная (я протестировал ее на iPad1 & iPad2).
Прошу совета, если вы знаете какое-то решение проблемы.

Спасибо.Flex Mobile - проблемы с производительностью TileLayout

ответ

3

Вообще плитка очень медленная в flex. Это связано с тем, что он позволяет изменять размер для каждого рендеринга элемента, а measure/updateDisplayList занимает много времени.

  1. Я бы предложил вам использовать собственный список плитки и удалить компонент flex в этом случае. Если вы используете его только для макета, вы можете вручную/программно вычислить позиции каждого элемента в плитке и перенести его в правый пиксель x/y.

  2. Также будьте осторожны с эффектами! Они могут нормально работать в симуляторе AIR или в браузере рабочего стола, но есть много ресурсов на мобильных устройствах.

  3. Если вы все еще хотите использовать компонент гибкой плитки, а не создавать свой собственный элемент управления плиткой, я предлагаю вам использовать фиксированные средства рендеринга для всех элементов, которые вы видите внутри, а также указать columnWidth и rowHeight внутри декларации TileList , Это может немного улучшить производительность ...

Кроме того, установка некоторого образца кода не повредит. Может быть, вы делаете что-то неправильно, а ... donno: лишняя недействительность, плохой рендеринг и т. Д.!

+0

Спасибо за совет, я собираюсь написать свой собственный компонент. После некоторой отладки я узнаю, что создано больше экземпляров itemRenderer, и это тяжелая часть. Для создания 70 экземпляров требуется около 20 сек, и они довольно просты, как IconItemRenderer. –

+0

Я предлагаю вам использовать только простую математику 2d для размещения рендереров/IconLoader. Я знаю это, проще просто использовать существующий элемент управления, но это не сложно реализовать и будет очень быстрым;) Btw, на каком устройстве вы тестируете? –

+0

Устройства iPad 1 и iPad 2. Да, запустите тест, где я обменял TiledLayout на UIComonent, которые создают спрайты и позиционируют их, разница нежелательна. –

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> 

Unfortunatly я не могу разделить источник FolderTileItem с вами, но его простой простой компонент, который расширяет Sprite.