flashapache-flexflex4blackberry-playbookflashlite

How do I implement a infinite list in Flex (hero)


I'm new to Flex/ActionScript (.NET/Java has been my main playground so far). I'm trying to build a Flex app that has a list that is meant to look and behave like an infinite list (of items - which can be any object). The idea is that the user should be able to scroll up or down and never reach the end of the list in either direction.

An example would be a list of numbers. Scrolling up will show negative numbers; scrolling down will show positive ones. Now, my list is a simple Flex Spark list (using Flex Hero). It is bound to a data provider that is an ArrayList.

My initial idea was to listen to the scroll event and add/remove items as needed. However, in the current build of Flex Hero, there is a bug that doesn't raise scroll events for vertical scrollbars sometimes (http://bugs.adobe.com/jira/browse/SDK-26533).

So, I'm using the workaround suggested in the link above (i.e listening to the propertyChanged event of the list's scroller's viewport. The event though only gives me the current verticalScrollPosition.

And it looks like the default spark list supports smooth scrolling and raises the event many times before the list scrolling comes to a standstill (it is a nice visual effect).

Now, I need to :

  1. Figure out whether it is scrolling up or down (how do I do that?)
  2. Figure out which items are visible. I can get this from:

    list.dataGroup.getItemIndicesInView()

  3. Add/remove items as needed, so that the user can scroll up and down forever, never reaching the end of the list in either direction.

I've tried the following code but it doesn't work. (comments in code).

Any Flex experts out there? Please help.


        import mx.collections.ArrayList;
        import mx.core.INavigatorContent;
        import mx.events.FlexEvent;
        import mx.events.PropertyChangeEvent;

        var listData:ArrayList;
        var firstItemInView:int = 0;
        var lastItemInView:int = 0;
        var count = 0;

                    //gets the currently visible item indices (first and last)
        private function getVisibleIndices():Vector.<int> { 
            var ind:Vector.<int> = new Vector.<int>(2, true);
            ind[0] = firstItemInView;
            ind[1] = lastItemInView;
            return ind;
        }

        protected function view_creationCompleteHandler(event:FlexEvent):void {

                            //create an initialise list data
            listData = new ArrayList();
            for(var i:int = 0; i < 6; i++){
                listData.addItemAt(i, i);
            }
            infiniteList.dataProvider = listData;

            updateIndices();

            infiniteList.scroller.viewport.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, infiniteList_VerticalScroll_PropertyChanged);              
        }

                    //get the indices visible from the list's data group
        private function getNewIndices():Vector.<int> {
            var indices:Vector.<int> = new Vector.<int>(2, true);
            var indicesInView:Vector.<int> = infiniteList.dataGroup.getItemIndicesInView();
            if (indicesInView.length > 0){
                indices[0] = indicesInView[0];
            } 
            if (indicesInView.length > 1){
                indices[1] = indicesInView[indicesInView.length - 1];
            }
            return indices;
        }

        private function updateIndices():void {
            var indices:Vector.<int> = getNewIndices();
            if (indices.length > 0){
                firstItemInView = indices[0];
                if (indices.length > 1){
                    lastItemInView = indices[1];
                }
            }
        }

        protected function leftCalendar_VerticalScroll_PropertyChanged(event:PropertyChangeEvent):void {


            switch (event.property){
                case "verticalScrollPosition":

                    var indices:Vector.<int> = getNewIndices();
                    var oldIndices:Vector.<int> = getVisibleIndices();

                    var newNum:Number;


                    if (indices[1] - indices[0] == 2 && (oldIndices[0] != indices[0] && oldIndices[1] != indices[1])){
                        //a new item is in view. did we scroll up or down?
                        if (oldIndices[0] < indices[0]){
                            count++;
                            trace(count + " up : old[" + oldIndices[0] + "," + oldIndices[1] + "], new[" + indices[0] + "," + indices[1] + "]");
                            //newNum = Number(listData.getItemAt(listData.length - 1)) + 1;
                            //trace("new number to add: " + newNum);
                            //trace("todo remove: " + listData.getItemAt(0));
                            fixItems({ addAt : "top", removeAt : "bottom", newValue : newNum});

                        } else {
                            trace("down : old[" + oldIndices[0] + "," + oldIndices[1] + "], new[" + indices[0] + "," + indices[1] + "]");                               
                            fixItems({ addAt : "bottom", removeAt : "top", newValue : newNum});
                        }

                        //update indices:
                        updateIndices();
                        var newOnes = getVisibleIndices(); //seems to be getting the new ones, but the next occurance of this event handler doesn't pick up the new values! why?
                        trace(count + " current[" + newOnes[0] + ", " + newOnes[1] + "]");
                    }

                    break;
            }
        }

        protected function fixItems(data:Object):void {
            var item:Object;

            //add a new item
            if (data.addAt == "top"){
                listData.addItemAt(data.newValue, 0);
            } else {
                listData.addItem(data.newValue);
            }

            //remove one of the existing ones
            if (data.removeAt == "top"){
                item = listData.getItemAt(0);
                trace("removing " + item);
                listData.removeItemAt(0);
            } else {
                item = listData.getItemAt(listData.length - 1);
                trace("removing " + item);
                listData.removeItemAt(listData.length - 1);
            }
            updateIndices();
        }


Solution

  • You can't use a List. You'll have to create your own custom component for this from scratch. All components that I know of in Flex uses a finite dataProvider to display information. If you want infinite, you'll need to create your own component that can handle a range (or none whatsoever) and display it appropriately and scroll it. Be sure to clean any items that are not displayed anymore (or reuse them) because that would be a severe memory leak.