Search code examples
xamllayoutscrollviewmauicollectionview

Handle scrolling and height with multiple collectionviews on one page in dotNET MAUI


I've got a horizontally oriented (on top) and a vertically oriented (below) CollectionView on the same page. I would like the page to scroll down infinitely but the bottom CollectionView does not allow this. For some reason it gets a small, fixed height and does not fill the page.

I tried both with and without a parent ScrollView. How do I make the page scroll infinitely with both the horizontally oriented and vertically oriented CollectionView on it?

(see code and illustrations below)

XAML attempt 1:

<VerticalStackLayout>
    <CollectionView>
        <CollectionView.ItemsLayout>
            <GridItemsLayout Orientation="Horizontal"/>
        </CollectionView.ItemsLayout>
    </CollectionView>
    <CollectionView>
        <CollectionView.ItemsLayout>
            <GridItemsLayout Orientation="Vertical"/>
        </CollectionView.ItemsLayout>
    </CollectionView>
</VerticalStackLayout>

XAML attempt 2:

<ScrollView>
    <VerticalStackLayout>
        <CollectionView>
            <CollectionView.ItemsLayout>
                <GridItemsLayout Orientation="Horizontal"/>
            </CollectionView.ItemsLayout>
        </CollectionView>
        <CollectionView>
            <CollectionView.ItemsLayout>
                <GridItemsLayout Orientation="Vertical"/>
            </CollectionView.ItemsLayout>
        </CollectionView>
    </VerticalStackLayout>
</ScrollView>

This is how my views are scrolling:
Notice how the vertically oriented CollectionView (V) does not fill the page.

wrong scrolling

This is how I want my views to scroll:
Notice how you can continue to scroll down the entire page infinitely.

right scrolling


Solution

  • It is indeed discouraged to nest scrollable views in .NET MAUI. Since ScrollViews are unrestrained, doing otherwise would make sizing children that are also unrestrained difficult.

    The closest you could probably get to your desired design (based on how I understand it) would be by constraining the outer ScrollView to scroll vertically only and then adding a Grid inside with two rows. In the first row, you could add a CollectionView that only allows scrolling horizontally, which at the same time must have a fixed height. In the second row, which could be infinite in size, you could then have a VerticalStackLayout coupled with a BindableLayout in order to display any data you want to bind to vertically:

    XAML

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage
      x:Class="NestedScrollSample.MainPage"
      xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
      xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
      xmlns:system="clr-namespace:System;assembly=System.Runtime"
      x:Name="MyPage">
    
      <ScrollView Orientation="Vertical">
    
        <Grid RowDefinitions="120,*">
    
          <CollectionView
            Grid.Row="0"
            HeightRequest="120"
            ItemsSource="{Binding MyStrings, Source={x:Reference MyPage}}"
            BackgroundColor="Orange">
            <CollectionView.ItemsLayout>
              <GridItemsLayout Orientation="Horizontal" />
            </CollectionView.ItemsLayout>
            <CollectionView.ItemTemplate>
              <DataTemplate x:DataType="system:String">
                <Grid
                  WidthRequest="300"
                  HeightRequest="100"
                  VerticalOptions="Center">
                  <Label Text="{Binding}" />
                </Grid>
              </DataTemplate>
            </CollectionView.ItemTemplate>
          </CollectionView>
    
          <VerticalStackLayout
            Grid.Row="1"
            BackgroundColor="Aqua"
            BindableLayout.ItemsSource="{Binding MyStrings, Source={x:Reference MyPage}}">
            <BindableLayout.ItemTemplate>
              <DataTemplate x:DataType="system:String">
                <Grid HeightRequest="300">
                  <Label Text="{Binding}" />
                </Grid>
              </DataTemplate>
            </BindableLayout.ItemTemplate>
          </VerticalStackLayout>
    
        </Grid>
    
      </ScrollView>
    
    </ContentPage>
    

    Code-behind

    using System.Collections.ObjectModel;
    
    namespace NestedScrollSample;
    
    public partial class MainPage : ContentPage
    {
        public ObservableCollection<string> MyStrings { get; }
    
        public MainPage()
        {
            MyStrings = new()
            {
                "abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz!"
            };
    
            InitializeComponent();
        }
    }
    

    This way, you can have a nested setup, which circumvents the issues that typically arise when doing so.

    Disclaimer: This code is purely for demonstration purposes, it doesn't follow any design guidelines, recommendations or development patterns. I also only tested this on Android and Windows, but it should work on all supported platforms like this.