Search code examples
xamlxamarinxamarin.forms

Scrolling when there are two CollectionViews in ContentPage


I have two CollectionViews in my ContentPage inside a StackLayout, one above the other. Each binds to a separate ItemsSource. Above each one I have a Label. At this point each one take up 50% of the screen and scrolls separately.

I would like everything to scroll as though it were one long list.

So I surrounded everything with a ScrollView. But then, depending on where you put your finger, the scroll may scroll the entire page (which is what I want) or just the current CollectionView.

It seems like there is no way to cancel the scroll capability of the CollectionView. Is that true? and if not, How should I set up my ContentPage ?

In the below example both CollectionViews have the same model and binding but in reality they will be different.

Here is the xaml:

<RefreshView
        x:DataType="local:AllRestaurantsViewModel"
        Command="{Binding LoadItemsCommand}"
        IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
        <ScrollView>
            <StackLayout>
                <Label
                    FontSize="Large"
                    HorizontalOptions="Center"
                    Text="Suggested Restaurants" />
                <CollectionView
                    x:Name="ItemsListView"
                    ItemsSource="{Binding SuggestedRestsComments}"
                    SelectionMode="None">
                    <CollectionView.ItemTemplate>
                        <DataTemplate>
                            <StackLayout Padding="10" x:DataType="model:SuggestedRestsComment">
                                <Label
                                    FontSize="16"
                                    LineBreakMode="NoWrap"
                                    Style="{DynamicResource ListItemTextStyle}"
                                    Text="{Binding restaurantName}" />
                                <Label
                                    FontSize="13"
                                    LineBreakMode="NoWrap"
                                    Style="{DynamicResource ListItemDetailTextStyle}"
                                    Text="{Binding CityName}" />
                                <StackLayout.GestureRecognizers>
                                    <TapGestureRecognizer
                                        Command="{Binding Source={RelativeSource AncestorType={x:Type local:ItemsViewModel}}, Path=ItemTapped}"
                                        CommandParameter="{Binding .}"
                                        NumberOfTapsRequired="1" />
                                </StackLayout.GestureRecognizers>
                            </StackLayout>
                        </DataTemplate>
                    </CollectionView.ItemTemplate>
                </CollectionView>


                <Label
                    FontSize="Large"
                    HorizontalOptions="Center"
                    Text="Existing Restaurants" />


                <CollectionView
                    x:Name="ItemsListView2"
                    ItemsSource="{Binding SuggestedRestsComments}"
                    SelectionMode="None">
                    <CollectionView.ItemTemplate>
                        <DataTemplate>
                            <StackLayout Padding="10" x:DataType="model:SuggestedRestsComment">
                                <Label
                                    FontSize="16"
                                    LineBreakMode="NoWrap"
                                    Style="{DynamicResource ListItemTextStyle}"
                                    Text="{Binding restaurantName}" />
                                <Label
                                    FontSize="13"
                                    LineBreakMode="NoWrap"
                                    Style="{DynamicResource ListItemDetailTextStyle}"
                                    Text="{Binding CityName}" />
                                <StackLayout.GestureRecognizers>
                                    <TapGestureRecognizer
                                        Command="{Binding Source={RelativeSource AncestorType={x:Type local:ItemsViewModel}}, Path=ItemTapped}"
                                        CommandParameter="{Binding .}"
                                        NumberOfTapsRequired="1" />
                                </StackLayout.GestureRecognizers>
                            </StackLayout>
                        </DataTemplate>
                    </CollectionView.ItemTemplate>
                </CollectionView>
            </StackLayout>
        </ScrollView>
    </RefreshView>

Solution

  • You could have a try with Custom CollectionViewRenderer to achieve that in each platform.

    For example, send a mesage in Forms:

    void OnButtonClicked(object sender, EventArgs e)
    {
        MessagingCenter.Send<object>(this, "StopScrollinng");
    }
    

    Then in iOS CustomCollectionViewRenderer class stop scrolling:

    public class CustomCollectionViewRenderer: CollectionViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<GroupableItemsView> e)
        {
            base.OnElementChanged(e);
    
            MessagingCenter.Subscribe<object>(this, "StopScrollinng", (sender) =>
            {
                // Do something whenever the "StopScrollinng" message is received
                if (Control != null)
                {
                    NSArray s = Control.ValueForKey(new NSString("_subviewCache")) as NSMutableArray;
                    UICollectionView c = s.GetItem<UICollectionView>(0);
                    c.SetContentOffset(c.ContentOffset, true);
                }
            });
        }
    }
    

    And in Android CustomCollectionViewRenderer class stop scrolling:

    public class CustomCollectionViewRenderer: CollectionViewRenderer
    {
        public CustomCollectionViewRenderer(Context context) : base(context)
        {
           
        }
    
        protected override void OnElementChanged(ElementChangedEventArgs<ItemsView> elementChangedEvent)
        {
            base.OnElementChanged(elementChangedEvent);
            MessagingCenter.Subscribe<object>(this, "StopScrollinng", (sender) =>
            {
                // Do something whenever the "StopScrollinng" message is received
    
                this.DispatchTouchEvent(MotionEvent.Obtain(SystemClock.UptimeMillis(), SystemClock.UptimeMillis(), MotionEventActions.Cancel, 0, 0, 0));
            });
        }
    }