Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ListView Scrolling With Horizontal and Vertical in xamarin.forms

I am stuck on creating a Listview that supports horizontal and vertical scrolling, like this:

enter image description here

I don't understand how to achieve this type of ListView in Xamarin forms.

like image 627
sumit sisodia Avatar asked Apr 04 '18 09:04

sumit sisodia


People also ask

How do I scroll with forms in xamarin?

Scrolling contentSet the Content property of ScrollView to the view you want to scroll. This is often a StackLayout , but it can be any view. Set the Orientation property of ScrollView to a member of the ScrollOrientation property, Vertical , Horizontal , or Both . The default is Vertical .

How do I create a horizontal scrolling container?

To enable horizontal scrolling, we can use the CSS property overflow-x. If we assign the value scroll to the overflow-x property of the container element, the browser will hide horizontally overflowing content and make it accessible via horizontal scrolling.

What is ScrollView layout?

In Android, a ScrollView is a view group that is used to make vertically scrollable views. A scroll view contains a single direct child only. In order to place multiple views in the scroll view, one needs to make a view group(like LinearLayout) as a direct child and then we can define many views inside it.


2 Answers

You can achieve Horizontal & Vertical Listview by Custom Control. Kindly Refer this Code,

1) Extend Scrollview Properties for renderized Listview

using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Input;
using Xamarin.Forms;
namespace ProjectName.CustomControls
{
public class HorizontalListview : ScrollView
    {
        public static readonly BindableProperty ItemsSourceProperty =
            BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(HorizontalListview), default(IEnumerable));

        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        public static readonly BindableProperty ItemTemplateProperty =
            BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(HorizontalListview), default(DataTemplate));

        public DataTemplate ItemTemplate
        {
            get { return (DataTemplate)GetValue(ItemTemplateProperty); }
            set { SetValue(ItemTemplateProperty, value); }
        }

        public event EventHandler<ItemTappedEventArgs> ItemSelected;

        public static readonly BindableProperty SelectedCommandProperty =
            BindableProperty.Create("SelectedCommand", typeof(ICommand), typeof(HorizontalListview), null);

        public ICommand SelectedCommand
        {
            get { return (ICommand)GetValue(SelectedCommandProperty); }
            set { SetValue(SelectedCommandProperty, value); }
        }

        public static readonly BindableProperty SelectedCommandParameterProperty =
            BindableProperty.Create("SelectedCommandParameter", typeof(object), typeof(HorizontalListview), null);

        public object SelectedCommandParameter
        {
            get { return GetValue(SelectedCommandParameterProperty); }
            set { SetValue(SelectedCommandParameterProperty, value); }
        }


        public void Render()
        {
            if (ItemTemplate == null || ItemsSource == null)
                return;

            var layout = new StackLayout();
           layout.Spacing = 0;

            layout.Orientation = Orientation == ScrollOrientation.Vertical ? StackOrientation.Vertical : StackOrientation.Horizontal;

            foreach (var item in ItemsSource)
            {
                var command = SelectedCommand ?? new Command((obj) =>
                {
                    var args = new ItemTappedEventArgs(ItemsSource, item);
                    ItemSelected?.Invoke(this, args);
                });
                var commandParameter = SelectedCommandParameter ?? item;

                var viewCell = ItemTemplate.CreateContent() as ViewCell;
                viewCell.View.BindingContext = item;
                viewCell.View.GestureRecognizers.Add(new TapGestureRecognizer
                {
                    Command = command,
                    CommandParameter = commandParameter,
                    NumberOfTapsRequired = 1
                });
                layout.Children.Add(viewCell.View);
            }

            Content = layout;
        }
    }

2) Include Namespace in your XAML Page

xmlns:control="clr-namespace:ProjectName"

3) Use Control in your Design

<control:HorizontalListview Orientation="Horizontal" x:Name="lst">                   
                    <control:HorizontalListview.ItemTemplate>
                        <DataTemplate>
                            <ViewCell>
                                <Label Style="{StaticResource lblPlaceNumberRing}" />
                            </ViewCell>
                        </DataTemplate>
                    </control:HorizontalListview.ItemTemplate>
                </control:HorizontalListview>

4) Make Renderer for Android

using System;
using System.ComponentModel;
using ProjectName;
using ProjectName.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;


[assembly: ExportRenderer(typeof(HorizontalListview), typeof(HorizontalListviewRendererAndroid))]

namespace ProjectName.Droid.Renderers
{
    public class HorizontalListviewRendererAndroid : ScrollViewRenderer
    {
        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);

            var element = e.NewElement as HorizontalListview;
            element?.Render();

            if (e.OldElement != null)
                e.OldElement.PropertyChanged -= OnElementPropertyChanged;

            e.NewElement.PropertyChanged += OnElementPropertyChanged;

        }

        protected void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (ChildCount > 0)
            {
                GetChildAt(0).HorizontalScrollBarEnabled = false;
                GetChildAt(0).VerticalScrollBarEnabled = false;
            }
        }
    }
}

5) Make Renderer for iOS

using System;
using UIKit;
using ProjectName.iOS;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using ProjectName;
using System.ComponentModel;

[assembly: ExportRenderer(typeof(HorizontalListview), typeof(HorizontalListviewRendererIos))]

namespace ProjectName.iOS.Renderers
{
    public class HorizontalListviewRendererIos : ScrollViewRenderer
    {
        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);

            var element = e.NewElement as HorizontalListview;
            element?.Render();
            if (e.OldElement != null)
                e.OldElement.PropertyChanged -= OnElementPropertyChanged;

            e.NewElement.PropertyChanged += OnElementPropertyChanged;
        }

        protected void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            this.ShowsHorizontalScrollIndicator = false;
            this.ShowsVerticalScrollIndicator = false;
            this.AlwaysBounceHorizontal = false;
            this.AlwaysBounceVertical = false;
            this.Bounces = false;

        }
       }
}
like image 105
Er Jainam Shah Avatar answered Oct 09 '22 23:10

Er Jainam Shah


I tried to just have a ScrollView around the ListView but that causes unpleasant behaviour if the ListView is responsible for the vertical scrolling while the ScrollView does the horizontal scrolling. So you have to ensure that the ListView doesn't scroll in any direction and the ScrollView does in both directions.

The latter is is easy to achive by just setting:

scrollView.Orientation = ScrollOrientation.Both;

Preventing the scrolling of the ListView is a bit more tricky. Apparently the only way to do it is to ensure that the ListView.HeightRequest is greater than it actually needs. I ended up doing it like this:

listView.HeightRequest = listView.RowHeight * (ListUsedAsItemSource.Count + 1) + 1;

The + 1 inside the brackets is to set a bit of height for the headers too. If you don't have headers you can omit it. Also be aware that you will have to set the listView.RowHeight manually (the default value is 40 I think). In my case without setting it on my own it was -1.

As a ListView doesn't scroll horizontally you don't have to prevent anything here. But per default the ListView will fit to the horizontal space. So you also have to set the WidthRequest similar to the HeightRequest above. Anyways, you won't be able to get something like a column width from the ListView so you'll have define a value on your own (and also use that value as the WidthRequest of your columns)

like image 37
Grochni Avatar answered Oct 10 '22 00:10

Grochni