feathersui / feathersui-starling

User interface components for Starling Framework and Adobe AIR
https://feathersui.com/learn/as3-starling/
Other
915 stars 386 forks source link

useVirtualLayout = true results in the creation of extra items inside the list items #1754

Closed denisgl7 closed 6 years ago

denisgl7 commented 6 years ago

A list of items that include another list

scheduleList = new List();
scheduleList.itemRendererType= EntryHorizontalDayItemRenderer 
var scheduleListLayout:HorizontalLayout = new HorizontalLayout();
scheduleListLayout.verticalAlign = VerticalAlign.JUSTIFY;
scheduleListLayout.horizontalAlign = HorizontalAlign.JUSTIFY;
scheduleListLayout.hasVariableItemDimensions = true;
scheduleList.layout = scheduleListLayout;

EntryHorizontalDayItemRenderer

timeList = new List();
timeList.setItemRendererFactoryWithID(TYPE_TIME, function ():IListItemRenderer
{
    return new EntryTimeItemRenderer();
});
timeList.setItemRendererFactoryWithID(TYPE_UNITED_TIME, function ():IListItemRenderer
{
        return new EntryUnitedTimeItemRenderer();
});
timeList.factoryIDFunction = function (item:Object):String
{
    if (item.type === TYPE_TIME)
      return TYPE_TIME;
    else if (item.type === TYPE_UNITED_TIME)
              return TYPE_UNITED_TIME;
}
var timeListLayout:VerticalLayout = new VerticalLayout();
timeListLayout.hasVariableItemDimensions = true;
timeListLayout.useVirtualLayout = true;
timeListLayout.horizontalAlign = HorizontalAlign.CENTER;
timeListLayout.gap = 7;
timeListLayout.paddingTop = timeListLayout.paddingBottom = 10;
timeList.layout = timeListLayout;
timeListLayout.useVirtualLayout = false;

the problem disappears

joshtynjala commented 6 years ago

I can't understand what's wrong in the GIF that you posted.

Regardless, one or more your item renderers probably aren't accounting for the fact that they may be reused for multiple different items as the list scrolls.

denisgl7 commented 6 years ago

List behavior when using virtualLayout = false

This is normal behavior. All data changes within each element that represents the column with the data. Why is the change in the data of one object reflected on every fifth element of the list when using VirtualLayout ?

joshtynjala commented 6 years ago

Because the item renderer is being reused as the list scrolls. It's your responsibility to update the contents of the item renderer when it receives a new item.

If I had to guess, you are probably forgetting to update the dataProvider of the nested list, so it's still showing the data for the old item.

denisgl7 commented 6 years ago

But I'm only changing data in one element. Why does this affect data from other items?

joshtynjala commented 6 years ago

Because the item renderer is being reused for a completely different item as the list scrolls. When useVirtualLayout is false, the item renderer is always "new". It has never displayed any data. When useVirtualLayout is true, the item may be "new" or it may have already rendered data for a completely different item.

Inside the List component, this is basically what's happening the first time it is asked to render an item:

var itemRenderer:DefaultListItemRenderer = new DefaultListItemRenderer();
itemRenderer.data = list.dataProvider.getItemAt(0);
itemRenderer.index = 0;

Later, after the List has scrolled a bit, it will try to reuse some of the item renderers that it already created. Now, let's say that the item renderer from the previous example is inside this Array named existingItemRenderers.

var itemRenderer:DefaultListItemRenderer = existingItemRenderers.pop();
itemRenderer.data = list.dataProvider.getItemAt(5);
itemRenderer.index = 5;

Your code inside the custom item renderer is not accounting for the possibility that the item renderer may not be "new". You need to reset the state of the item renderer. I'm guessing that you may need to update the dataProvider of the timeList any time that the data property of the item renderer has changed. Assuming that you're using LayoutGroupListItemRenderer, you'd probably do that inside commitData().

denisgl7 commented 6 years ago

Josh, please look at this. timeList is updated in commitData

override protected function commitData():void
{
    if (this._data && this._owner)
      {     
        var currentDate:Date = DateUtils.returnNormalizedDate(_data.date);  
        var currentSelectedTimes:Array = _data.currentSelectedTimes;

        var timeObject:Object = _data.timeArray;
        var timeArray:Array = [];
        var nowDate:Date = new Date();
        for (var key:String in timeObject)
        {
            var time:Object = timeObject[key];
            var date:Date = DateUtils.returnNormalizedDate(time.date);
            if (time.status == RequestStatus.FREELY && date.getTime() >= nowDate.getTime())
            {   
                var flag:Boolean = false;
                if (currentSelectedTimes && currentSelectedTimes.length != 0)
                    for (var i:int = 0; i < currentSelectedTimes.length; i++)
                    {
                    var array:Array = currentSelectedTimes[i];
                    var startDate:Date = DateUtils.returnNormalizedDate(array[0].date);
                    var endDate:Date = DateUtils.returnNormalizedDate(array[array.length - 1].date);
                    if (date.time >= startDate.time && date.time <= endDate.time)
                    {
                        flag = true;
                        break;
                    }
                }
            if (timeArray.indexOf(time) == -1 && !flag)
            {
                time.type = TYPE_TIME;
                timeArray.push(time);
            }
        }

           }
       if (unitedTimesObjectsArray && unitedTimesObjectsArray.length != 0)
        for (i = 0; i < unitedTimesObjectsArray.length; i++)
        {
            timeArray.push(unitedTimesObjectsArray[i]);
        }
        timeArray.sortOn('time');

        if (timeArray.length == 0)
        {
            this.removeFromParent(true);
            return;
        }

        timeList.dataProvider = new ArrayCollection(timeArray);
     }
}
joshtynjala commented 6 years ago

Could it be that you are accidentally using the same unitedTimesObjectsArray with multiple different items?

denisgl7 commented 6 years ago

I found the problem, it was that I was using an internal unitedtimesobjectsarray array. After I added it to _data, everything was fine. It is strange that some elements used internal arrays of other elements. This led me to a dead end. Thanks for your help, Josh! I should get a better look at the list.

joshtynjala commented 6 years ago

I'm happy that you figured it out!

Adrian-S commented 6 years ago

My solution to this since I decided I can't hold the amount of data I had in memory, was to mark the item that was under edit, and replace it with a duplicate. When I would come back to the edited one I would remove the duplicate and show the edited one.

Data for each column is retrieved after the horizontal slider would come to a semi-stop, and then rendered after stop. Apparently sliding with a lot of elements is bad for FPS.