How to implement Horizontal ListView in Xamarin Forms.

DeepakRNathDeepakRNath USMember ✭✭

We want to implement horizontal listview in our xamarin forms application, How it is possible?
Any other solution for this, please share.
Thanks in advance...

Answers

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    I think a carouselview could help you... or not?

  • NMackayNMackay GBInsider, University mod

    The paid controls mostly support this but if your looking for a free one there's flowlistview

    https://github.com/daniel-luberda/DLToolkit.Forms.Controls/tree/master/FlowListView

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    There are some open-source horizontal controls that can do that.
    Or, you can rotate the ListView 90 degrees and (optionally) rotate the ListViewItems (via LIstViewItemTemplate) -90 degrees. This has the advantage of not needing a 3rd party control, but you have a do a little extra work setting it up.

  • LearnEverythingLearnEverything USMember ✭✭✭
    edited November 2017

    Source code
    https://github.com/MigueldeIcaza1/HorizontalListView

    namespace HorizontalListView.CustomControls
    {
        public delegate void RepeaterViewItemAddedEventHandler(object sender, RepeaterViewItemAddedEventArgs args);
        public class RepeaterView : Grid
        {
            #region Properties
            public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
              propertyName: "ItemsSource",
              returnType: typeof(IEnumerable),
              declaringType: typeof(RepeaterView),
              defaultValue: null,
              defaultBindingMode: BindingMode.OneWay,
              propertyChanged: ItemsChanged);
            public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
              propertyName: "ItemTemplate",
              returnType: typeof(DataTemplate),
              declaringType: typeof(RepeaterView),
              defaultValue: default(DataTemplate));
            public static readonly BindableProperty NoOfColumnsProperty = BindableProperty.Create(
               propertyName: "NoOfColumns",
               returnType: typeof(string),
               declaringType: typeof(RepeaterView),
               defaultValue: "2",
               defaultBindingMode: BindingMode.OneWayToSource);
            public event RepeaterViewItemAddedEventHandler ItemCreated;
            public IEnumerable ItemsSource
            {
                get { return (IEnumerable)GetValue(ItemsSourceProperty); }
                set { SetValue(ItemsSourceProperty, value); }
            }
            public DataTemplate ItemTemplate
            {
                get { return (DataTemplate)GetValue(ItemTemplateProperty); }
                set { SetValue(ItemTemplateProperty, value); }
            }
            public string NoOfColumns
            {
                get { return (string)GetValue(NoOfColumnsProperty); }
                set { SetValue(NoOfColumnsProperty, value); }
            }
            #endregion
            private static void ItemsChanged(BindableObject bindable, object oldValue, object newValue)
            {
                IEnumerable oldValueAsEnumerable;
                IEnumerable newValueAsEnumerable;
                try
                {
                    oldValueAsEnumerable = oldValue as IEnumerable;
                    newValueAsEnumerable = newValue as IEnumerable;
                }
                catch (Exception e)
                {
                    throw e;
                }
    
                var control = (RepeaterView)bindable;
                var oldObservableCollection = oldValue as INotifyCollectionChanged;
    
                if (oldObservableCollection != null)
                {
                    oldObservableCollection.CollectionChanged -= control.OnItemsSourceCollectionChanged;
                }
    
                var newObservableCollection = newValue as INotifyCollectionChanged;
    
                if (newObservableCollection != null)
                {
                    newObservableCollection.CollectionChanged += control.OnItemsSourceCollectionChanged;
                }
    
                control.Children.Clear();
                int rowCounter = -1;
                int columnCounter = 0;
    
                int requiredColumns = control.CheckIfItIsInt();
    
                for (int i = 0; i < requiredColumns; i++)
                {
                    control.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
                }
    
                if (newValueAsEnumerable != null)
                {
                    foreach (var item in newValueAsEnumerable)
                    {
                        var view = control.CreateChildViewFor(item);
    
                        if (columnCounter % requiredColumns == 0)
                        {
                            control.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
                            rowCounter++;
                            columnCounter = 0;
                        }
                        control.Children.Add(view, columnCounter, rowCounter);
                        columnCounter++;
                        control.OnItemCreated(view);
                    }
                }
                control.UpdateChildrenLayout();
                control.InvalidateLayout();
            }
            private int CheckIfItIsInt()
            {
                int requiredColumns;
                var isInt = int.TryParse(this.NoOfColumns, out requiredColumns);
                if (!isInt) return 1;
                return requiredColumns;
            }
            protected virtual void OnItemCreated(View view) =>
                this.ItemCreated?.Invoke(this, new RepeaterViewItemAddedEventArgs(view, view.BindingContext));
            private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                var invalidate = false;
                if (e.OldItems != null)
                {
                    this.Children.RemoveAt(e.OldStartingIndex);
                    invalidate = true;
                }
                if (e.NewItems != null)
                {
                    for (var i = 0; i < e.NewItems.Count; ++i)
                    {
                        var item = e.NewItems[i];
                        var view = this.CreateChildViewFor(item);
    
                        this.Children.Insert(i + e.NewStartingIndex, view);
                        OnItemCreated(view);
                    }
                    invalidate = true;
                }
                if (invalidate)
                {
                    this.UpdateChildrenLayout();
                    this.InvalidateLayout();
                }
            }
            private View CreateChildViewFor(object item)
            {
                this.ItemTemplate.SetValue(BindableObject.BindingContextProperty, item);
                return (View)this.ItemTemplate.CreateContent();
            }
        }
        public class RepeaterViewItemAddedEventArgs : EventArgs
        {
            private readonly View view;
            private readonly object model;
            public RepeaterViewItemAddedEventArgs(View view, object model)
            {
                this.view = view;
                this.model = model;
            }
            public View View => this.view;
            public object Model => this.model;
        }
    

    ViewModel

    public List<Community> CommunitiesList { get; set; }
    
    public class Community
        {
            public string CommunityName { get; set; }
            public string CreatedDate { get; set; }
            public IEnumerable<Person> Persons { get; set; }
        }
        public class Person
        {
            public string Name { get; set; }
            public string City { get; set; }
        }
    

    Xaml

    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:customControls="clr-namespace:HorizontalListView.CustomControls"
                 x:Class="HorizontalListView.ListViewPage"
                 Title="Horizonatal ListView" >
        <ContentPage.Resources>
            <ResourceDictionary>
                <Style x:Key="CommunityNameStyle" TargetType="Label">
                    <Setter Property="FontSize" Value="Large" />
                    <Setter Property="TextColor" Value="#0a9bb5" />
                </Style>
                <Style x:Key="CommunityCreatedStyle" TargetType="Label">
                    <Setter Property="FontSize" Value="Small" />
                    <Setter Property="TextColor" Value="#364f6b"/>
                    <Setter Property="VerticalOptions" Value="CenterAndExpand"/>
                </Style>
            </ResourceDictionary>
        </ContentPage.Resources>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <ListView Grid.Row="1"
                        HasUnevenRows="True"
                        BackgroundColor="#f3f3f3"
                        SeparatorVisibility="None"
                        ItemsSource="{Binding CommunitiesList}"
                        SelectedItem="{Binding SelectedCommunity}">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <Grid Padding="5">
                                <Grid BackgroundColor="White" Padding="5">
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto"/>
                                        <RowDefinition Height="Auto"/>
                                        <RowDefinition Height="*"/>
                                    </Grid.RowDefinitions>
                                    <Label  Text="{Binding CommunityName}" Style="{StaticResource CommunityNameStyle}"/>
                                    <Label  Grid.Row="1"  Text="{Binding CreatedDate}" Style="{StaticResource CommunityCreatedStyle}" />
                                    <customControls:RepeaterView  Grid.Row="2" 
                                                          ItemsSource="{Binding Persons}"
                                                          NoOfColumns="5">
                                        <customControls:RepeaterView.ItemTemplate>
                                            <DataTemplate>
                                                <StackLayout HeightRequest="150" WidthRequest="150"  Spacing="0" >
                                                    <Image  Source="https://int-dir.s3.amazonaws.com/uploads/1311_1311_pipelinedeals-icon-100px-72px.png"></Image>
                                                    <Label Text="{Binding Name}" TextColor="#bc5c29"/>
                                                    <Label Text="{Binding City}" TextColor="Gray" />
                                                </StackLayout>
                                            </DataTemplate>
                                        </customControls:RepeaterView.ItemTemplate>
                                    </customControls:RepeaterView>
                                </Grid>
                            </Grid>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>
    </ContentPage>
    

  • LearnEverythingLearnEverything USMember ✭✭✭
    edited November 2017

    Modified code with support SelectionItemEvent for Category and Item from list

    public delegate void SelectedItemEventHandLer(object sender, SelectionTapItemEventArgs args);
        public delegate void RepeaterViewItemAddedEventHandler(object sender, RepeaterViewItemAddedEventArgs args);
        public class HorizontalListControl : Grid
        {
            #region Properties
            public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
              propertyName: "ItemsSource",
              returnType: typeof(IEnumerable),
              declaringType: typeof(HorizontalListControl),
              defaultValue: null,
              defaultBindingMode: BindingMode.OneWay,
              propertyChanged: ItemsChanged);
            public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
              propertyName: "ItemTemplate",
              returnType: typeof(DataTemplate),
              declaringType: typeof(HorizontalListControl),
              defaultValue: default(DataTemplate));
            public static readonly BindableProperty NoOfColumnsProperty = BindableProperty.Create(
               propertyName: "NoOfColumns",
               returnType: typeof(string),
               declaringType: typeof(HorizontalListControl),
               defaultValue: "2",
               defaultBindingMode: BindingMode.OneWayToSource);
            public event RepeaterViewItemAddedEventHandler ItemCreated;
            public IEnumerable ItemsSource
            {
                get { return (IEnumerable)GetValue(ItemsSourceProperty); }
                set { SetValue(ItemsSourceProperty, value); }
            }
            public DataTemplate ItemTemplate
            {
                get { return (DataTemplate)GetValue(ItemTemplateProperty); }
                set { SetValue(ItemTemplateProperty, value); }
            }
            public string NoOfColumns
            {
                get { return (string)GetValue(NoOfColumnsProperty); }
                set { SetValue(NoOfColumnsProperty, value); }
            }
            #endregion
            private static void ItemsChanged(BindableObject bindable, object oldValue, object newValue)
            {
                IEnumerable oldValueAsEnumerable;
                IEnumerable newValueAsEnumerable;
                try
                {
                    oldValueAsEnumerable = oldValue as IEnumerable;
                    newValueAsEnumerable = newValue as IEnumerable;
                }
                catch (Exception e)
                {
                    throw e;
                }
                var control = (HorizontalListControl)bindable;
                var oldObservableCollection = oldValue as INotifyCollectionChanged;
                if (oldObservableCollection != null)
                {
                    oldObservableCollection.CollectionChanged -= control.OnItemsSourceCollectionChanged;
                }
                var newObservableCollection = newValue as INotifyCollectionChanged;
    
                if (newObservableCollection != null)
                {
                    newObservableCollection.CollectionChanged += control.OnItemsSourceCollectionChanged;
                }
                control.Children.Clear();
                int rowCounter = -1;
                int columnCounter = 0;
                int requiredColumns = control.CheckIfItIsInt();
    
                for (int i = 0; i < requiredColumns; i++)
                {
                    control.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
                }
                if (newValueAsEnumerable != null)
                {
                    foreach (var item in newValueAsEnumerable)
                    {
                        var view = control.CreateChildViewFor(item);
                        if (columnCounter % requiredColumns == 0)
                        {
                            control.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
                            rowCounter++;
                            columnCounter = 0;
                        }
                        control.Children.Add(view, columnCounter, rowCounter);
                        columnCounter++;
                        control.OnItemCreated(view);
                       //Added custom selection for category and its list
                        var Tap = new TapGestureRecognizer();
                        view.GestureRecognizers.Add(Tap);
                        Tap.Tapped += (g, r) =>
                        {
                            if (control.SelectedItemChanged != null)
                            {
                                control.OnItemSelection(new SelectionTapItemEventArgs(control.BindingContext, view.BindingContext));
                            }
                        };
    
                    }
                }
                control.UpdateChildrenLayout();
                control.InvalidateLayout();
            }
            //Added item and its list tap item event
            public event SelectedItemEventHandLer SelectedItemChanged;
            public virtual void OnItemSelection(SelectionTapItemEventArgs e)
            {
                if (this.SelectedItemChanged != null)
                {
                    this.SelectedItemChanged(this, e);
                }
            }
            private int CheckIfItIsInt()
            {
                int requiredColumns;
                var isInt = int.TryParse(this.NoOfColumns, out requiredColumns);
                if (!isInt) return 1;
                return requiredColumns;
            }
            protected virtual void OnItemCreated(View view) =>
                this.ItemCreated?.Invoke(this, new RepeaterViewItemAddedEventArgs(view, view.BindingContext));
            private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                var invalidate = false;
                if (e.OldItems != null)
                {
                    this.Children.RemoveAt(e.OldStartingIndex);
                    invalidate = true;
                }
                if (e.NewItems != null)
                {
                    for (var i = 0; i < e.NewItems.Count; ++i)
                    {
                        var item = e.NewItems[i];
                        var view = this.CreateChildViewFor(item);
    
                        this.Children.Insert(i + e.NewStartingIndex, view);
                        OnItemCreated(view);
                    }
                    invalidate = true;
                }
                if (invalidate)
                {
                    this.UpdateChildrenLayout();
                    this.InvalidateLayout();
                }
            }
            private View CreateChildViewFor(object item)
            {
                this.ItemTemplate.SetValue(BindableObject.BindingContextProperty, item);
                return (View)this.ItemTemplate.CreateContent();
            }
        }
        public class RepeaterViewItemAddedEventArgs : EventArgs
        {
            private readonly View view;
            private readonly object model;
            public RepeaterViewItemAddedEventArgs(View view, object model)
            {
                this.view = view;
                this.model = model;
            }
            public View View => this.view;
            public object Model => this.model;
        }
        //Added new selection args
        public class SelectionTapItemEventArgs : EventArgs
        {
            private readonly object Selecteditem;
            private readonly object SelectedSubitem;
            public SelectionTapItemEventArgs(object selected, object subitem)
            {
                this.Selecteditem = selected;
                this.SelectedSubitem = subitem;
            }
            public object SelectedCategory => this.Selecteditem;
            public object SelectedItem => this.SelectedSubitem;
        }
    
     private void HorizontalListControl_SelectedItemChanged(object sender, Controls.SelectionTapItemEventArgs args)
            {
                var item = args.SelectedCategory as Community;
                var sub = args.SelectedItem as Person;
                this.DisplayAlert(item.CommunityName, sub.Name, "Close");
            }
    

    Result

    I am just absolute beginner in C#

  • AlfonsiAlfonsi USMember ✭✭✭

    You can wait for the upcoming CollectionView or use my HorizontalListView.

    It has:

    • Snapping on first or middle element
    • Padding and item spacing
    • Handles NotifyCollectionChangedAction Add, Remove and Reset actions
    • View recycling
    • RecyclerView on Android
    • UICollectionView on iOS
  • gl93gl93 Member

    If you are using Xamarin forms 3.5 and above you can use a Bindable Layouts , see official docs (I can't post link because I'm new on this forum)

    This is an example from Xamarin official docs:

    <StackLayout BindableLayout.ItemsSource="{Binding User.TopFollowers}"
                 Orientation="Horizontal"
                 ...>
        <BindableLayout.ItemTemplate>
            <DataTemplate>
                <controls:CircleImage Source="{Binding}"
                                      Aspect="AspectFill"
                                      WidthRequest="44"
                                      HeightRequest="44"
                                      ... />
            </DataTemplate>
        </BindableLayout.ItemTemplate>
    </StackLayout>
    
  • ShahramOmidvarShahramOmidvar USMember
    edited September 2019

    you can use a CollectionView

  • SreeeeSreeee INMember ✭✭✭✭✭

    @NMackay said:
    The paid controls mostly support this but if your looking for a free one there's flowlistview

    https://github.com/daniel-luberda/DLToolkit.Forms.Controls/tree/master/FlowListView

    hi @NMackay
    Which property of flowlistview needs to use for creating a horizontal listview?

  • NMackayNMackay GBInsider, University mod

    @Sreeee said:

    @NMackay said:
    The paid controls mostly support this but if your looking for a free one there's flowlistview

    https://github.com/daniel-luberda/DLToolkit.Forms.Controls/tree/master/FlowListView

    hi @NMackay
    Which property of flowlistview needs to use for creating a horizontal listview?

    Flowlistview does not support this scenario as far as I'm aware. Syncfusion sfListview does support it well and there is now CollectionView so you need to evaluate what components you use for this scenario.

  • SreeeeSreeee INMember ✭✭✭✭✭

    @NMackay Thanks for the perfect solution :)

    Adding My codes here, it may help others :)

    Xaml

    <CollectionView 
                 HeightRequest="30"
                 SelectionMode="Single"
                 SelectionChanged="ItemTapped"
            ItemsSource="{Binding Items}"
                 x:Name="collectionview"
                 ItemsLayout="HorizontalList">
                 <CollectionView.ItemTemplate>
                       <DataTemplate>
                              <StackLayout Margin="5">
                                      <Label
                                           TextColor="Black"
                                           FontSize="Large"
                                           HorizontalTextAlignment="Center"
                                           VerticalTextAlignment="Center"
                                           Text="{Binding title}"/>
                                 </StackLayout>
                            </DataTemplate>
                     </CollectionView.ItemTemplate>
              </CollectionView>
    

    Xaml.cs

    public async void ItemTapped(object sender, SelectionChangedEventArgs e)
            {
                var selectedItem = (e.CurrentSelection.FirstOrDefault() as MyModel);
                if (selectedItem != null)
                {
                    //Do action
                }
            }
    

    Add below code in AppDelegate class on iOS and MainActivity class on Android, before calling Forms.Init:

    Forms.SetFlags("CollectionView_Experimental");
    
  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    My only concern with that is handling 'SelectionChanged' as if it were an ItemTapped.
    1. You'll get selection changed even if you change the selected item in code. So if your logic pre-highlights something at launch, you'll get the event, then you'll handle the event as if the user picked it.
    2. If it is SelectionChanged don't handle it in an ItemTapped named event. That's confusing. And makes life harder when you choose to handle and actual tapped event on the time.
    3. You seem to be setting up to do logic from the UI code behind. You're not deeply there yet, but it looks like the direction you're going.

    Personally, I'd just handle an actual Tap, on the item, with a Command that is handled in the ViewModel. You can also send the item itself as the CommandParameter so the ViewModel handler receives the object and is properly kept ignorant of the UI.

  • SreeeeSreeee INMember ✭✭✭✭✭

    @NMackay and @ClintStLaurent

    Using CollectionView, the Horizontal listview and its tappings are working fine on the android device. Yesterday I tested it on an iPhone and the UI is not showing like the android. The items are showing on one top of another. Attaching a screenshot below:

    I already added my codes on top, am I missing something in IOS? I have added Forms.SetFlags("CollectionView_Experimental"); in AppDelegate.cs before Forms.Init.

  • SreeeeSreeee INMember ✭✭✭✭✭

    @Sreeee said:
    @NMackay and @ClintStLaurent

    Using CollectionView, the Horizontal listview and its tappings are working fine on the android device. Yesterday I tested it on an iPhone and the UI is not showing like the android. The items are showing on one top of another. Attaching a screenshot below:

    I already added my codes on top, am I missing something in IOS? I have added Forms.SetFlags("CollectionView_Experimental"); in AppDelegate.cs before Forms.Init.

    Upgraded XF to 4.4.0.991210-pre2 solves this problem.
    https://github.com/xamarin/Xamarin.Forms/issues/7714#issuecomment-553799822

  • Divya_RDivya_R USMember ✭✭

    @LearnEverything Thanks your code was really helpfull..can you please tel me how horizontal scrolling of only list item can be achieved ?

  • igorkr_10igorkr_10 Member ✭✭✭

    @Divya_R said:
    @LearnEverything Thanks your code was really helpfull..can you please tel me how horizontal scrolling of only list item can be achieved ?

    Do you mean scrolling per one list item? CarouselView?

  • Divya_RDivya_R USMember ✭✭
    edited January 13

    @igorkr_10 yes Scrolling one list item keeping Community and created date label fixed as shown in the image by @LearnEverything

Sign In or Register to comment.