Search code examples
xamarin.formscollectionview

Xamarin forms: How to add an underline only for the selected item in Collectionview


I have a horizontal collectionview with some item, I need an underline only for the selected item.

I tried like below:

MainPage.xaml.cs

public partial class MainPage : ContentPage
{
    public List<Category> categoryList { get; set; }
    public MainPage()
    {
        InitializeComponent();
        categoryList = new List<Category>();
        SetItems(true, false, false, false, false, false);
    }

    private void SetItems(bool v1, bool v2, bool v3, bool v4, bool v5, bool v6)
    {
        categoryList.Add(new Category() { Title = "itme1", IsSelected = v1 });
        categoryList.Add(new Category() { Title = "itme2", IsSelected = v2 });
        categoryList.Add(new Category() { Title = "itme3", IsSelected = v3 });
        categoryList.Add(new Category() { Title = "itme4", IsSelected = v4 });
        categoryList.Add(new Category() { Title = "itme5", IsSelected = v5 });
        categoryList.Add(new Category() { Title = "itme6", IsSelected = v6 });
        Category_collectionview.ItemsSource = categoryList;
    }

    public void CategoryTapped(object sender, SelectionChangedEventArgs e)
    {
        var selectedItem = (e.CurrentSelection.FirstOrDefault() as Category);
        if (selectedItem != null)
        {
            if (selectedItem.Title == "itme1")
            {
                SetItems(true, false, false, false, false, false);
            }
            else if (selectedItem.Title == "itme2")
            {
                SetItems(false, true, false, false, false, false);
            }
            else if (selectedItem.Title == "itme3")
            {
                SetItems(false, false, true, false, false, false);
            }
        }
        Category_collectionview.SelectedItem = null;
    }
}

public class Category 
{
    public string Title { get; set; }
    public bool IsSelected { get; set; }
}

MainPage.xaml

<CollectionView 
        SelectionMode="Single"
        SelectionChanged="CategoryTapped"
        x:Name="Category_collectionview"
        ItemsLayout="HorizontalList">
        <CollectionView.ItemTemplate>
            <DataTemplate>
                <StackLayout 
                    Orientation="Vertical"
                    Margin="5">

                    <Label
                        TextColor="Black"
                        HorizontalTextAlignment="Center"
                        VerticalTextAlignment="Center"
                        Text="{Binding Title}">
                        <Label.FontSize>
                            <OnIdiom x:TypeArguments="x:Double">
                                <OnIdiom.Phone>16</OnIdiom.Phone>
                                <OnIdiom.Tablet>32</OnIdiom.Tablet>
                                <OnIdiom.Desktop>16</OnIdiom.Desktop>
                            </OnIdiom>
                        </Label.FontSize>
                    </Label>

                    <BoxView 
                        HorizontalOptions="FillAndExpand"
                        BackgroundColor="Black"
                        IsVisible="{Binding IsSelected}"
                        HeightRequest="2"/>
                </StackLayout>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>

But the boxview is always on the first item, what I am missing here?


Solution

  • You need to implement the interface INotifyPropertyChanged in model so that you can update the UI in runtime

    public class Category : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
    
    
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        public string Title { get; set; }
    
        bool isSelected;
        public bool IsSelected { get {
    
                return isSelected;
    
            }
            set
            {
                if(value!= isSelected)
                {
                    isSelected = value;
                    OnPropertyChanged("IsSelected");
                }
            }
        }
    }
    

    In addition , the CategoryTapped event will not work in your case. You could use TapGestureRecognizer

    <CollectionView 
            SelectionMode="Single"
            
            x:Name="Category_collectionview"
            ItemsSource="{Binding categoryList}"
            ItemsLayout="HorizontalList">
                <CollectionView.ItemTemplate>
    
    <DataTemplate>
         <StackLayout 
               Orientation="Vertical"
               Margin="5">
    
              <StackLayout.GestureRecognizers>
                 <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" />
              </StackLayout.GestureRecognizers>
              //...
    

    And in code behind

    Note : It would be better to use ObservableCollection instead of List . And it is not a good design to reset ItemsSource each time .

    public partial class MainPage : ContentPage
        {
            public ObservableCollection<Category> categoryList { get; set; }
            public MainPage()
            {
                InitializeComponent();
                categoryList = new ObservableCollection<Category>();
                SetItems(true, true, false, false, false, false);
                BindingContext = this;
            }
    
            private void SetItems(bool v1, bool v2, bool v3, bool v4, bool v5, bool v6)
            {
                categoryList.Add(new Category() { Title = "itme1", IsSelected = v1 });
                categoryList.Add(new Category() { Title = "itme2", IsSelected = v2 });
                categoryList.Add(new Category() { Title = "itme3", IsSelected = v3 });
                categoryList.Add(new Category() { Title = "itme4", IsSelected = v4 });
                categoryList.Add(new Category() { Title = "itme5", IsSelected = v5 });
                categoryList.Add(new Category() { Title = "itme6", IsSelected = v6 });
                Category_collectionview.ItemsSource = categoryList;
               
            }
    
            private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
            {
                var obj = sender as StackLayout;
    
                var currentItems = obj.BindingContext as Category;
    
                foreach (Category category in categoryList)
                {
                    category.IsSelected = false;
    
                    if (currentItems == category)
                    {
                        category.IsSelected = true;
                    }
                }
    
                Category_collectionview.SelectedItem = null;
            }
        }
    

    enter image description here