🏗️
MauiReactor
  • What is MauiReactor?
  • What's new in Version 2
  • What's new in Version 3
  • Getting Started
  • Getting Started Version 2
  • Components
    • State-less Components
    • Stateful Components
      • Inline Components
    • Component life-cycle
    • Component Properties
    • Component with children
    • Component Parameters
    • Theming
    • Navigation
      • NavigationPage
      • Shell
      • Back button
    • Controls
      • Button
      • RadioButton
      • FlyoutPage
      • CollectionView
        • Interactions
        • Layout
        • Selection
        • Empty view
        • Scrolling
        • Grouping
      • IndicatorView
      • Picker
      • Shell
      • Label
    • Wrap 3rd party controls
      • Lottie animations
      • Provide DataTemplate to native controls
    • Accessing native controls
    • Animation
      • Property-Based
      • AnimationController
      • Timer
    • Graphics
      • CanvasView control
    • Window
    • Testing
    • XAML Integration
  • Deep dives
    • Native tree and Visual tree
    • Dependency injection
    • Working with the GraphicsView
    • Migrating from MVVM Model
    • Using XAML Resources
    • Behaviors
  • resources
    • Source and Sample Applications
  • Q&A
    • How to deal with state shared across Components?
    • Does this support ObservableCollection for CollectionView?
    • Do we need to add states to create simple animations such as ScaleTo, FadeTo, etc on tap?
    • How to deal with custom dialogs/popups?
  • How to create a Menu/ContextMenu?
Powered by GitBook
On this page

Was this helpful?

Edit on GitHub
  1. Components
  2. Controls
  3. CollectionView

Scrolling

This page provides instructions on how scroll to a specific item in the collection

PreviousEmpty viewNextGrouping

Last updated 1 year ago

Was this helpful?

The .NET Multi-platform App UI (.NET MAUI) defines two ScrollTo methods, that scroll items into view. One of the overloads scrolls the item at the specified index into view, while the other scrolls the specified item into view. Both overloads have additional arguments that can be specified to indicate the group the item belongs to, the exact position of the item after the scroll has completed, and whether to animate the scroll.

In MauiReactor, you need a reference to the native CollectionView and call the ScrollTo method on it.

The following sample code shows how to scroll to a specific position index:

public record IndexedPersonWithAddress(int Index, string FirstName, string LastName, int Age, string Address);

class MainPageScrollingState
{
    public List<IndexedPersonWithAddress> Persons { get; set; }

    public MauiControls.ItemsViewScrolledEventArgs LatestEventArgs { get; set; }

    public int ScrollToIndex {  get; set; }

    public MauiControls.ScrollToPosition ScrollToPosition { get; set; }

    public bool IsAnimationDisabled { get; set; }
}

class MainPageScrolling : Component<MainPageScrollingState>
{
    private MauiControls.CollectionView _collectionViewRef;

    private static List<IndexedPersonWithAddress> GenerateSamplePersons(int count)
    {
        var random = new Random();
        var firstNames = new[] { "John", "Jim", "Joe", "Jack", "Jane", "Jill", "Jerry", "Jude", "Julia", "Jenny" };
        var lastNames = new[] { "Brown", "Green", "Black", "White", "Blue", "Red", "Gray", "Smith", "Doe", "Jones" };
        var cities = new[] { "New York", "London", "Sidney", "Tokyo", "Paris", "Berlin", "Mumbai", "Beijing", "Cairo", "Rio" };

        var persons = new List<IndexedPersonWithAddress>();

        for (int i = 0; i < count; i++)
        {
            var firstName = firstNames[random.Next(0, firstNames.Length)];
            var lastName = lastNames[random.Next(0, lastNames.Length)];
            var age = random.Next(20, 60);
            var city = cities[random.Next(0, cities.Length)];
            var address = $"{city} No. {random.Next(1, 11)} Lake Park";

            persons.Add(new IndexedPersonWithAddress(i, firstName, lastName, age, address));
        }

        return persons;
    }

    protected override void OnMounted()
    {
        State.Persons = GenerateSamplePersons(100);
        base.OnMounted();
    }

    public override VisualNode Render()
    {
        return new ContentPage
        {
            new Grid("Auto, Auto,*", "*")
            {
                new VStack(spacing: 5)
                {
                    new Label($"CenterItemIndex: {State.LatestEventArgs?.CenterItemIndex}"),
                    new Label($"LastVisibleItemIndex: {State.LatestEventArgs?.LastVisibleItemIndex}"),
                    new Label($"FirstVisibleItemIndex: {State.LatestEventArgs?.FirstVisibleItemIndex}"),
                    new Label($"VerticalDelta: {State.LatestEventArgs?.VerticalDelta}"),
                    new Label($"VerticalOffset: {State.LatestEventArgs?.VerticalOffset}"),
                },

                new VStack(spacing: 5)
                {
                    new Entry()
                        .Keyboard(Keyboard.Numeric)
                        .OnTextChanged(_ =>
                        {
                            if (int.TryParse(_, out var scrollToIndex) &&
                                scrollToIndex >= 0 &&
                                scrollToIndex < State.Persons.Count)
                            {
                                SetState(s => s.ScrollToIndex = scrollToIndex);
                            }
                        }),

                    new Picker()
                        .ItemsSource(Enum.GetValues<MauiControls.ScrollToPosition>().Select(_=>_.ToString()).ToArray())
                        .OnSelectedIndexChanged(index => SetState(s => s.ScrollToPosition = (MauiControls.ScrollToPosition)index)),                        

                    new HStack(spacing: 5)
                    {
                        new CheckBox()
                            .OnCheckedChanged((sender, args) => SetState(s => s.IsAnimationDisabled = args.Value)),

                        new Label("Disable animation")
                            .HFill()
                            .VCenter(),
                    },

                    new Button("Scroll To")
                        .OnClicked(()=> _collectionViewRef?.ScrollTo(State.ScrollToIndex, position: State.ScrollToPosition, animate: !State.IsAnimationDisabled))
                }
                .GridRow(1),

                new CollectionView(collectionViewRef => _collectionViewRef = collectionViewRef)
                    .ItemsSource(State.Persons, RenderPerson)
                    .GridRow(2)
                    .OnScrolled(OnScrolled)
            }
        };
    }

    private void OnScrolled(object sender, MauiControls.ItemsViewScrolledEventArgs args)
    {
        SetState(s => s.LatestEventArgs = args);
    }

    private VisualNode RenderPerson(IndexedPersonWithAddress person)
    {
        return new VStack(spacing: 5)
        {
            new Label($"{person.Index}. {person.FirstName} {person.LastName} ({person.Age})"),
            new Label(person.Address)
                .FontSize(12)
        }
        .HeightRequest(56)
        .VCenter();
    }
}

The above sample could be easily modified to scroll to a specific item as well.

Control scroll position when new items are added

You can control the behavior of the scrolling when new items are added to the CollectionView using the property ItemsUpdatingScrollMode. For example, setting it to MauiControls.ItemsUpdatingScrollMode.KeepLastItemInView, the CollectionView will scroll to the latest item added.

This is an example:

class MainPageScrollingModeState
{
    public ObservableCollection<IndexedPersonWithAddress> Persons { get; set; }

    public MauiControls.ItemsUpdatingScrollMode CurrentScrollMode { get; set; }
}

class MainPageScrollingMode : Component<MainPageScrollingModeState>
{
    private MauiControls.CollectionView _collectionViewRef;

    private static List<IndexedPersonWithAddress> GenerateSamplePersons(int count)
    {
        var random = new Random();
        var firstNames = new[] { "John", "Jim", "Joe", "Jack", "Jane", "Jill", "Jerry", "Jude", "Julia", "Jenny" };
        var lastNames = new[] { "Brown", "Green", "Black", "White", "Blue", "Red", "Gray", "Smith", "Doe", "Jones" };
        var cities = new[] { "New York", "London", "Sidney", "Tokyo", "Paris", "Berlin", "Mumbai", "Beijing", "Cairo", "Rio" };

        var persons = new List<IndexedPersonWithAddress>();

        for (int i = 0; i < count; i++)
        {
            var firstName = firstNames[random.Next(0, firstNames.Length)];
            var lastName = lastNames[random.Next(0, lastNames.Length)];
            var age = random.Next(20, 60);
            var city = cities[random.Next(0, cities.Length)];
            var address = $"{city} No. {random.Next(1, 11)} Lake Park";

            persons.Add(new IndexedPersonWithAddress(i, firstName, lastName, age, address));
        }

        return persons;
    }

    protected override void OnMounted()
    {
        State.Persons = new ObservableCollection<IndexedPersonWithAddress>(GenerateSamplePersons(100));
        base.OnMounted();
    }

    public override VisualNode Render()
    {
        return new ContentPage
        {
            new Grid("Auto, Auto, *", "*")
            {
                new Picker()
                    .ItemsSource(Enum.GetValues<MauiControls.ItemsUpdatingScrollMode>().Select(_=>_.ToString()).ToArray())
                    .OnSelectedIndexChanged(index => SetState(s => s.CurrentScrollMode = (MauiControls.ItemsUpdatingScrollMode)index)),                    

                new Button("Add item", ()=> State.Persons.Add(GenerateSamplePersons(1).First() with { Index = State.Persons.Count }))
                    .GridRow(1),

                new CollectionView()
                    .ItemsSource(State.Persons, RenderPerson)
                    .ItemsUpdatingScrollMode(State.CurrentScrollMode)
                    .GridRow(2)
            }
        };
    }

    private VisualNode RenderPerson(IndexedPersonWithAddress person)
    {
        return new VStack(spacing: 5)
        {
            new Label($"{person.Index}. {person.FirstName} {person.LastName} ({person.Age})"),
            new Label(person.Address)
                .FontSize(12)
        }
        .HeightRequest(56)
        .VCenter();
    }
}
CollectionView
Scrolling collection view to a specific position index
Sample app showing the ItemsUpdatingScrollMode behavior