Forum Xamarin.Forms

ListView Scrolled Events

DH_HA1DH_HA1 USMember ✭✭✭
edited December 2016 in Xamarin.Forms


Provide a ListView Scrolled event identical to the ScrollView. This will allow apps to apply animations like parallax on the ListView Header for example.

API Changes


In order to facilitate this an Event is added to the ListView called Scrolled with the same event args as a ScrollView.

public event EventHandler<ScrolledEventArgs> Scrolled { add; remove; }

Intended Use Case

This will allow apps to apply animations on scroll like parallax on the ListView Header for example.


Rejected · Last Updated


  • TheRealJasonSmithTheRealJasonSmith USXamarin Team Xamurai

    Im not actually sure the Scroll offsets are readily available on all platforms. I mean Im sure estimates are but Im not sure we can actually get proper numbers that will reliably monotonically increase as you scroll down even when estimated values end up being wrong.

    A quick spike providing this is possible on iOS/UWP/Android would be enough to get a +1 for me.

  • AdrianKnightAdrianKnight USMember ✭✭✭✭

    I don't believe Android provides scroll delta on ListView. I've seen hacky solutions that did not work for everyone. If you are trying to create a parallax effect, then you're looking at CoordinatorLayout and its friends as suggested here.

  • TheRealJasonSmithTheRealJasonSmith USXamarin Team Xamurai

    A quick googling seems to confirm Adrian is right, the ListView does not provide this information on Android.

    Unless someone else has an idea how to solve that reliably and in a way that wont be a maintenance nightmare I think we will have to reject this proposal.

  • DH_HA1DH_HA1 USMember ✭✭✭

    Ideally I would like to be able to perform animations on the ListView Header based on scrolling. I know there are other use cases for it as well.

    I will dig up the apis

  • AdrianKnightAdrianKnight USMember ✭✭✭✭
    edited December 2016

    That listener provides everything except for scroll delta. If you want visible item changes, then XF already has ItemAppearing and ItemDisappearing. Scroll state flags are also useless in my opinion. When implementing a parallax, you probably do not need just a simple Scrolled Y/N flag but also the amount/velocity/etc.

  • TheRealJasonSmithTheRealJasonSmith USXamarin Team Xamurai

    That gives when views become visible, not the X/Y offset. Visible view tracking is not what the ScrolledEventArgs does.

  • DH_HA1DH_HA1 USMember ✭✭✭

    True but it could be computed but that might be expensive.

  • TheRealJasonSmithTheRealJasonSmith USXamarin Team Xamurai

    It can't be computed without realizing and measuring every cell in the UnevenRows case :/

  • DavidDancyDavidDancy AUMember ✭✭✭✭

    I did something a long time ago that works "well enough" for me in the Android case. See here

    The essence of the idea (which I got from somewhere on the interwebs but I regret I forgot where) is that you measure the vertical offset of the first child of the ListView. It's not quite the same accuracy as the iOS scroll position, but it's easily good enough to do what we need.

    I'm sorry I have no idea how to do the equivalent (or if it's even possible) in Windows.

  • TheRealJasonSmithTheRealJasonSmith USXamarin Team Xamurai

    How does that work once you start recycling?

  • DavidDancyDavidDancy AUMember ✭✭✭✭

    Ha, good question! I am on an old version of Xamarin Forms in the project from which I extracted that code, so I'm sorry I don't know. However since it's calling the Android API on the underlying Android.Widget.ListView using GetChildAt(0) to get the first element in the ListView I would hope that all the dots would join up internally and everything would still work.

  • TheRealJasonSmithTheRealJasonSmith USXamarin Team Xamurai

    Yeah that wont work anymore :/

  • DavidDancyDavidDancy AUMember ✭✭✭✭

    We could still keep track of the top element internally though, no?

  • TheRealJasonSmithTheRealJasonSmith USXamarin Team Xamurai

    not really, there is not guarantee that the ListView will start at the top. The top cell may never actually be displayed.

  • DavidDancyDavidDancy AUMember ✭✭✭✭
  • TheRealJasonSmithTheRealJasonSmith USXamarin Team Xamurai
    edited December 2016

    GetChildAt(0) will return the 0th child of the listview which is currently realized. Its Y position will be largely unrelated to the current Y position of the list as the whole thing is just kind of faked on a sliding surface.

    While it will work in some limited cases, I can't imagine how it could possibly work with larger truly virtualized lists. At this point though, I guess someone will have to just try and see!

  • DavidDancyDavidDancy AUMember ✭✭✭✭

    I'll have a go and report back.

  • TheRealJasonSmithTheRealJasonSmith USXamarin Team Xamurai

    We do not believe this is possible. I am rejecting the proposal but will leave it open in case anyone comes back with a proof of concept showing we can make it work reliably.

  • VelocityVelocity NZMember ✭✭✭

    We have now implemented this on iOS and UWP, and are currently working on a (reliable!) Android implementation.
    Once we have this working on Android, will post back here.

  • DH_HA1DH_HA1 USMember ✭✭✭

    @Velocity will you put up on github?

  • VelocityVelocity NZMember ✭✭✭
    edited March 2017

    @DH_HA1 Sure thing, we got an initial version working on Android this morning but it still needs more work.
    Once it's stabilized, will add it to our XF Controls repo.

  • VelocityVelocity NZMember ✭✭✭
    edited March 2017

    @DH_HA1 @TheRealJasonSmith OK, we now have a stabilized version of our extended ListView with scroll events.
    Please see portable control here and renderers below.

    This is working great on all three platforms. Tested with both RetainElement and RecycleElement caching strategies, HasUnevenRows=true/false. We're now using it to achieve a parallax effect behind a scrolling ListView.

    iOS and UWP implementations are a breeze. Android is a little more tricky as native AbsListView has no concept of scroll events, but with a little bit of math and view tracking we can do a fairly nice job. Added total offset calculation and compensation for display density.

    Kudos to Budius on this StackOverflow post. Was able to base the view tracking and offset calculation engine on his Java implementation.

  • DavidDancyDavidDancy AUMember ✭✭✭✭

    @Velocity Budius' comment at the end of the SO post is telling. Perhaps the Android ListView widget is simply the wrong one to use altogether. I know we're limited by Xamarin.Forms' original choices, and I think your contribution is an excellent workaround, but we should probably be concentrating on scrolling views that are based on UICollectionView, RecyclerView and whatever it is in WinPhone instead, rather than flogging the dead UITableView / ListView horses. I've even seen some in Swift/Obj-C development circles recommend abandoning UITableView now too.

  • VelocityVelocity NZMember ✭✭✭

    @DavidDancy said:
    @Velocity Budius' comment at the end of the SO post is telling. Perhaps the Android ListView widget is simply the wrong one to use altogether. I know we're limited by Xamarin.Forms' original choices, and I think your contribution is an excellent workaround, but we should probably be concentrating on scrolling views that are based on UICollectionView, RecyclerView and whatever it is in WinPhone instead, rather than flogging the dead UITableView / ListView horses. I've even seen some in Swift/Obj-C development circles recommend abandoning UITableView now too.

    I do agree on some respects, however that really is a separate issue. It could be argued that RecyclerView is a worthy replacement for AbsListView, however there is nothing wrong with using UITableView on iOS and ListView on UWP. Their collection view cousins UICollectionView and GridView work great, but are not designed as a native list view replacement.

    I still agree that a separate collection view is needed which is why I proposed it in a separate thread (Note, we have also built this and is working). The purpose of this request is simply to extend an existing control already available within Xamarin.Forms.

  • 15mgm1515mgm15 USMember ✭✭✭✭

    @Velocity thanks so much for this, I was able to create a sticky header on Xamarin.Android/Forms without any 3rd party implementation! (This is a task that took me 4 days to complete.)

  • TobyKTobyK GBMember ✭✭✭

    @Velocity Thanks dude - your code helped me a lot understanding the android side of it - I've now managed to extend the returned eventargs to include IsAtBottom parameter and using a 'firstVisibleItem + visibleItemCount == totalItemCount' condition in the OnScroll() to set it and send via your framework. Works a treat, great stuff!

  • JoonChoiJoonChoi AUUniversity ✭✭
    edited February 2019

    I always thought nesting ListView into a ScrollView was a big nono but it actually worked pretty well for me. you can rely on the Scrolled event from the ScrollView instead in this nested setup. I was able to achieve the Parallax effect using this method. Note that the ListView's background color is set to transparent and I've reserved 200px high StackLayout in the header of the ListView. In the background of the ListView, there I placed the .

           <Grid x:Name="mainGrid" RowSpacing="0">
                    <RowDefinition Height="Auto" /> 
                    <RowDefinition Height="*" />
                    <RowDefinition Height="Auto" />
                <!-- CUSTOM NAVBAR -->
                <local:CustomNavBar Grid.Row="0" x:Name="navBar" LeftButtonContent="left_arrow" LeftButtonClicked="OnLeftButtonClicked" Title="{Binding DiscoverByTitle}" />
                <Grid Grid.Row="0" Grid.RowSpan="2"  HeightRequest="300" VerticalOptions="Start" HorizontalOptions="FillAndExpand">
                    <Image x:Name="hero" Source="{Binding StoreProduct.Images[0].Src}" Aspect="AspectFill"></Image>
                    <Label IsVisible="{Binding ShowStoreInfo}" Text="Store info goes here" VerticalOptions="End"></Label>
                    <Button IsVisible="{Binding ShowStoreInfo}" WidthRequest="100" Text="Follow this Store" Command="{Binding FollowStoreCommand}" VerticalOptions="End" HorizontalOptions="End"></Button>
                <ScrollView Grid.Row="1" x:Name="scrollView" >                    
                        SeparatorColor="{ DynamicResource ListViewSeparatorColor }" 
                        ItemsSource="{Binding Products}" 
                        ItemTemplate="{StaticResource discoverByTemplateSelector}"
                            <StackLayout IsVisible="{Binding ShowStoreInfo}" HeightRequest="200">
                            <StackLayout VerticalOptions="FillAndExpand" BackgroundColor="White" HeightRequest="800">-->
                               <!-- <StackLayout.Effects>                    
                                    <CLControls:SafeAreaPaddingEffect TopOrBottom="Bottom" />                
                                </StackLayout.Effects> -->
            <local:CartButtonView Grid.Row="2" VerticalOptions="End" x:Name="cartButton" IsVisible="{Binding CartIsNotEmpty}"></local:CartButtonView>
                <local:CustomActivityIndicator BackgroundColor="White" x:Name="activityIndicator" Grid.Row ="1" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"></local:CustomActivityIndicator>

    The scrolled event doesn't work well when you have a footer section in the listview for some reason. Therefore, it was commented out in the Xaml above. The event handler example is as below. Just an example for you, that may not be a Parallax algorithm but this was good enough for me.

                scrollView.Scrolled += async (object sender, ScrolledEventArgs e) =>
                    if (offsetY == -1000)
                        offsetY = e.ScrollY;
                    if (e.ScrollY <= offsetY)
                        double scale = (225 - (e.ScrollY - offsetY)) / 225;
                        hero.Scale = scale;
                        hero.TranslationY = (e.ScrollY - offsetY) / 2;
                        //carousel.Layout(new Rectangle(e.ScrollY, e.ScrollY, CL.Controls.CLUtilities.ScreenSize.Width - e.ScrollY, 225 - e.ScrollY));
                        //gradientMask.Layout(new Rectangle(0, e.ScrollY, CL.Controls.CLUtilities.ScreenSize.Width - e.ScrollY, 225 - e.ScrollY));
                        //backIcon.TranslationY = e.ScrollY;
  • VelocityVelocity NZMember ✭✭✭

    If anyone would like the code for the custom Android renderer with scroll events, here it is.

Sign In or Register to comment.