Home > Flex 4 Examples > Flex 4 List remembering verticalScrollPosition

Flex 4 List remembering verticalScrollPosition

A simple requirement, but ~6 hours of frustration.

All I wanted to do was to remember the scroll position that the user was at after an update of the dataprovider items on a list occurred. I had a solution in flex 3 for the (Advanced)DataGrid control that listened for a scroll event and remembered the last known scroll position that would then be set in commit properties after the dataProvider for the grid dispatched a COLLECTION_CHANGE event.

Naturally, I started with that same solution on the Flex 4 list. It was a little trickier, discovering that no event was fired from the list when a scroll event occurred. It turns out that in the skin class, you have to listen to the vertical scroll bar on the scroller object, but this of course is entirely dependent on where your scroller or viewport container that handles scrolling lives, regardless of skinclass.

So once setup, and listening for the event and remembering the scroll position, I still noticed that the list would scroll to the top of the list before snapping back to where it was – LAME.

After a couple of hours of tinkering and messing around, and a couple of failed attempts at figuring out what was setting the vertical scroll position to 0, I had an “I’VE HAD IT!” moment. I finally decided to watch the Datagroup (the scroller’s viewport) in stead of the scroller itself and low and behold, after setting up the scenario with a breakpoint finally in the right place, the following was discovered:

spark.components::DataGroup::commitProperties()

            // Don't reset the scroll positions until the new ItemRenderers have been
            // created, see bug https://bugs.adobe.com/jira/browse/SDK-23175
            if (dataProviderChanged)
            {
                dataProviderChanged = false;
                verticalScrollPosition = horizontalScrollPosition = 0;
            }

That’s lame. What are the places where dataProviderChanged is set to true? The exact condition I was using to set the scroll position to what I wanted. Guess what, the data provider hadn’t changed like their bug suggests, but only a refresh of the provider in stead.

The fix?

package
{
	import mx.collections.IList;
	import mx.events.CollectionEvent;
	
	import spark.components.DataGroup;
	
	public class DataGroup extends spark.components.DataGroup
	{
		private var _dataProviderChanged:Boolean;
		private var _lastScrollPosition:Number = 0;
		
		public function DataGroup()
		{
			super();
		}
		
		override public function set dataProvider(value:IList):void
		{
			if ( dataProvider != null && value != dataProvider )
			{
				dataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, onDataProviderChanged);
			}
			super.dataProvider = value;
			
			if ( value != null )
			{
				value.addEventListener(CollectionEvent.COLLECTION_CHANGE, onDataProviderChanged);
			}
		}
		
		override protected function commitProperties():void
		{
			var lastScrollPosition:Number = _lastScrollPosition;
			
			super.commitProperties();
			
			if ( _dataProviderChanged )
			{
				verticalScrollPosition = lastScrollPosition;
			}
		}
		
		private function onDataProviderChanged(e:CollectionEvent):void
		{
			_dataProviderChanged = true;
			invalidateProperties();
		}
		
		
		override public function set verticalScrollPosition(value:Number):void
		{
			super.verticalScrollPosition = value;
			_lastScrollPosition = value;
		}
	}
}

I’ve created a bug report for this if anyone is interested.

Advertisements
  1. June 18, 2010 at 7:24 PM

    Does it help to listen for scroll position changes via the layout rather than a scrollbar?

    See http://forums.adobe.com/message/2872718 for an example.

    • roustalski
      June 22, 2010 at 7:56 AM

      It doesn’t help the problem described in the post, but I do think that is a better solution than listening to the vertical scroll bar on the scroller.

  2. October 21, 2010 at 1:06 PM

    Thanks for finding this out, I’m implementing something similar tomorrow, maybe this will speed it up a bit.

  3. James
    June 10, 2011 at 6:55 AM

    How would you implement this in a mx component?

  4. June 22, 2011 at 9:27 AM

    I try to implement this using a (spark) DataGrid with a custom Grid class implementing your methods and assigned to the DataGrid in a custom skin. However, it doesn’t work and wonder what could be the difference.

    DataGroup and Grid are both subclasses of GroupBase, I guess this is why I could copy and paste your code into the custom Grid class. But there seems to be a difference preventing your solution to work. Do you have an idea?

    • August 31, 2011 at 8:45 AM

      Sorry Rico,

      Nothing stands out from your description, but if you want to email me your project or a sample, I don’t mind taking a look at it.

      • Ramses
        October 17, 2012 at 7:30 AM

        Maybe a bit late, but i stombled upon this problem just now 🙂

        My fix for this problem:

        package classes
        {
        import mx.collections.IList;

        import spark.components.Grid;

        public class GridRemeberScrollPosition extends Grid
        {
        private var _dataProviderChanged:Boolean;
        private var _lastScrollPosition:Number = 0;

        public function GridRemeberScrollPosition()
        {
        super();
        }

        override public function set dataProvider(value:IList):void
        {
        super.dataProvider = value;
        _dataProviderChanged = true;
        validateNow();
        invalidateProperties();
        }

        override protected function commitProperties():void
        {
        var lastScrollPosition:Number = _lastScrollPosition;

        super.commitProperties();

        if(_dataProviderChanged)
        {
        verticalScrollPosition = lastScrollPosition;
        _dataProviderChanged = false;
        }
        }

        override public function set verticalScrollPosition(value:Number):void
        {
        super.verticalScrollPosition = value;
        if(value > 0)
        {
        _lastScrollPosition = value;
        }
        }
        }
        }

  5. Nad
    September 25, 2011 at 4:39 AM

    Hi;
    I’m not sure how to use the provided package. I’ve created a DataGroup.as file in my default package folder and pasted the code package code there. But that had no effect on my project.

    Please assist.

    • roustalski
      September 25, 2011 at 1:32 PM

      Hello,

      Did you create a skinclass for the s:List control and then in the custom skin class reference the DataGroup.as file in the skin?

  6. Nad
    September 26, 2011 at 1:10 AM

    Thanks for the tip, I’ve tried the following, but didn’t get far:
    [1]
    Created a skin for the s:List (File-> New-> MXML Skin) and
    – named it List_Skin,
    – Host Component -> List
    – Create Copy of -> spark.skins.spark.ListSkin
    [2]
    in the skin file (List_Skin.mxml) I:
    – Added: import DataGroup;
    – Searched for <s:DataGroup and replaced it with <local:DataGroup

    But it didn't compile then complaining about within the DataGroup.

    If it’s not much trouble, would you please attach to the article a sample project. I’m sure it will help many, as I couldn’t find any reference in the Internet for remembering scroll position based on COLLECTION_CHANGE other than this one.

    Thanks

  7. Nad
    September 27, 2011 at 3:11 AM

    Thanks;
    I’ve got it to work by using <local:layout instead of <s:layout in the List_Skin file. It now reads:

    Thanks you very much for your help
    /Nad

  8. Max
    September 14, 2012 at 8:48 AM

    Hi Roustalski
    I have a similar issue which I want to resolve but cant get your example working. The link to the zip file is no longer working – can you put it up somewhere again pls?

  9. lolninja
    September 18, 2012 at 8:12 AM

    This helped me a lot. Thanks.

    I ended up copying your class and applying a new skin, just as suggested. Do you want me to leave some sort of credit in the code of the new DataGroup class?

    • September 18, 2012 at 12:04 PM

      No problem. No need to give credit.

  10. Darshan Gopinath
    January 16, 2013 at 4:55 AM

    Thanks a lot for the post. Helped me immensely!! 🙂

  11. November 24, 2013 at 9:21 PM

    Hey Roustalski,

    Thanks for your efforts in finding a solution for this. Just wanted to inform that i am going to use a simpler version of your solution in our commercial project.

    i had to simplify it because in my case, change in data provider was actually setting the scroll bar back, so had to remove that check. Which also eliminated the need to listen for COLLECTION_CHANGE event at first place
    also changed it from vertical to horizontal scroller.

    here’s the end result
    —————–
    package
    {
    import mx.collections.IList;
    import mx.events.CollectionEvent;

    import spark.components.DataGroup;

    public class DataGroup extends spark.components.DataGroup
    {
    private var _lastScrollPosition:Number = 0;

    override protected function commitProperties():void
    {
    var lastScrollPosition:Number = _lastScrollPosition;
    super.commitProperties();
    horizontalScrollPosition = lastScrollPosition;
    }

    override public function set horizontalScrollPosition(value:Number):void
    {
    super.horizontalScrollPosition = value;
    _lastScrollPosition = value;
    }

    }
    }

  12. Victor
    March 16, 2014 at 5:39 AM

    hi, this link is no longer working https://bugs.adobe.com/jira/browse/SDK-26745
    can you please share this project somewhere?

  1. August 2, 2010 at 2:09 PM
  2. June 14, 2011 at 6:39 AM

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: