2

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();
        }

Krishna
  • 2,997
  • 1
  • 23
  • 31

3 Answers3

2

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.

J_A_X
  • 12,857
  • 1
  • 25
  • 31
  • ^^^ what he said. You will most certainly need to implement your own scrolling logic. – drkstr Apr 13 '11 at 23:45
  • Thanks, I've to the same conclusion too. The biggest problem has been (so far) to clean up items not in view. – Krishna Apr 16 '11 at 01:43
  • 1
    You should look into [component lifecycle](http://flexcomps.wordpress.com/2008/05/09/flex-component-life-cycle/). There are many resources online for this. You might also want to look at the [Flex in a week](http://www.adobe.com/devnet/flex/videotraining.html) series. – J_A_X Apr 17 '11 at 04:17
0

May try InfiniteScrollList class::

package components
{

import model.InfiniteListModel;
import model.LoadingVO;

import mx.core.ClassFactory;
import mx.events.PropertyChangeEvent;

import spark.components.IconItemRenderer;
import spark.components.List;

import views.itemRenderer.LoadingItemRenderer;

public class InfiniteScrollList extends List
{
    override protected function createChildren():void
    {
        super.createChildren();
        scroller.viewport.addEventListener( PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler );
        itemRendererFunction = itemRendererFunctionImpl;
    }   

    protected function propertyChangeHandler( event : PropertyChangeEvent ) : void
    {
        //trace( event.property, event.oldValue, event.newValue );

        if ( event.property == "verticalScrollPosition" ) 
        {
            if ( event.newValue == ( event.currentTarget.measuredHeight - event.currentTarget.height )) 
            {
                fetchNextPage();
            }
        }
    }

    protected function fetchNextPage() : void
    {
        if ( dataProvider is InfiniteListModel )
            InfiniteListModel( dataProvider ).getNextPage();
    }

    private function itemRendererFunctionImpl(item:Object):ClassFactory 
    {
        var cla:Class = IconItemRenderer;
        if ( item is LoadingVO )
            cla = LoadingItemRenderer;
        return new ClassFactory(cla);
    }
}
}

InfiniteListModel class:

package model
{
    import flash.events.Event;
import flash.utils.setTimeout;

import mx.collections.ArrayCollection;
import mx.rpc.AsyncToken;
import mx.rpc.Responder;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.RemoteObject;

public class InfiniteListModel extends ArrayCollection
{
    private var _remoteObject : RemoteObject;

    protected var _loading : Boolean = false;

    public function get remoteObject():RemoteObject
    {
        return _remoteObject;
    }

    public function set remoteObject(value:RemoteObject):void
    {
        _remoteObject = value;
        if ( _remoteObject )
            getNextPage();
    }

    public function InfiniteListModel(source:Array=null)
    {
        super(source);
        addItem( new LoadingVO() );
    }

    public function getNextPage() : void
    {
        if ( !_loading)
        {
            _loading = true;

            trace( "fetching data starting at " + (this.length-1).toString() );
            var token : AsyncToken = remoteObject.getData( this.length-1 );
            var responder : Responder = new Responder( resultHandler, faultHandler );
            token.addResponder( responder );
        }
    }

    protected function resultHandler(event:ResultEvent):void
    {
        this.disableAutoUpdate();

        if ( this.getItemAt( this.length-1 ) is LoadingVO )
            this.removeItemAt( this.length-1 );

        for each ( var item : * in event.result )
        {
            addItem( item );
        }
        addItem( new LoadingVO() );
        this.enableAutoUpdate();

        _loading = false;
    }

    protected function faultHandler(event:FaultEvent):void
    {
        trace( event.fault.toString() );
    }
}
}

For details, please refer to the following: http://www.tricedesigns.com/2011/10/26/infinitely-scrolling-lists-in-flex-applications/

michael
  • 1,160
  • 7
  • 19
0

I think you need to create your custom List control, which circulate arraycollection/arraylist item for UI, ArrayCollection/Array/ArrayList should only be use to hold data like liner list of items, and list Control should display items in circular fashion

EDITED I am trying to ans your question modifying data in list and direction of scroll hopefully this will help

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    <mx:Script>
        <![CDATA[
            import mx.controls.Alert;
            import mx.events.ScrollEvent;

            [Bindable]
            private var arr:Array = new Array(0,1,2,3,4,5);

            private var lastScrollPostion:int =0;
            private var isScrollingUp:Boolean = false;
            private function ltClicked(event:MouseEvent):void
            {
                arr.unshift(arr[0]-1);
                lst.dataProvider = arr;
            }
            private function rtClicked(event:MouseEvent):void
            {
                arr.push(arr[arr.length-1] +1); 
                lst.dataProvider = arr;
            }

            private function scrolled(event:ScrollEvent):void
            {
                if (lastScrollPostion < event.position)
                {
                    isScrollingUp = false;
                }
                else
                {
                    isScrollingUp = true;
                }
                lastScrollPostion = event.position;
                Alert.show("isScrollingUp : "+isScrollingUp.toString());
            }

        ]]>
    </mx:Script>
    <mx:VBox>
        <mx:List 
            id="lst" 
            width="100%" 
            dataProvider="{arr}"
            scroll="{scrolled(event)}"
            />
        <mx:HBox>
            <mx:Button id="lt" label="&lt;&lt;" click="{ltClicked(event)}"/>
            <mx:Button id="rt" label="&gt;&gt;" click="{rtClicked(event)}"/>
        </mx:HBox>

    </mx:VBox>


</mx:Application>

NOTE Function ltClicked and rtClicked are modifying List data and scrolled is used get to direction

I think better way of doing this is extend List control to get direction and scroll position and manipulate Array to add or remove items in list

Imran
  • 2,906
  • 1
  • 19
  • 20
  • Not sure that would work. How would you display the list of whole numbers -infinity to +infinity using a list? – Krishna Apr 12 '11 at 09:07
  • For whole numbers is hould be calculated value, calculated and update on user click. and new value is push/unshift in array – Imran Apr 12 '11 at 09:52
  • Thanks - but the update should happen on scroll - not user click. Also, an implementation / example will help. I'm not struggling with the concept / idea / logic. It's the implementation and actual APIs to use that I need - since I'm new to the Flex platform - but I've done lots of .NET/Silverlight. – Krishna Apr 12 '11 at 10:07
  • Thanks again - but I'm using a Flex 4.5 Spark List - not an mx list. The spark list gives me a smooth animated scrolling effect. (I'm building an application for a tablet device - the Blackberry Playbook, so the scrolling effect is required). I use a very similar logic to yours but doesn't work the way I want. – Krishna Apr 12 '11 at 11:48
  • okies, are you trying to implement contact list type list, if yes you can use **scrollposition** to move scroll on top and bottom – Imran Apr 12 '11 at 11:51
  • I had to accept the other guy's answer. Your idea was right - but the code is not something I can use given that my question was about using Flex spark controls not the older mx ones. – Krishna Apr 18 '11 at 00:50