Accordion ListView in Xamarin Forms

Accordion ListView or Expanded ListView, a list view that can show another list view inside its list view item, is one of UI components that Xamarin Forms doesn’t have it by default. So, in this post, I want to discuss one of the simplest way to create an accordion list view look alike in Xamarin Forms.

Custom Observable Collection

One property that we need the most to create accordion list view is visibility property with boolean data type. This property will determine wether the children list view is visible or not. So, when user tap to an item in list view, that property have to change. And because it’s only a property in one of the list view items, we gonna need custom observable collection that support that condition. Because regular observable collection won’t be support it. The property will change, but it won’t affect the UI view. I have discussed this custom observable collection in first post, and we gonna need it again in this case.


public class CustomObservableCollection : ObservableCollection
{
    public CustomObservableCollection() { }
    public CustomObservableCollection(IEnumerable items) : this()
    {
        foreach (var item in items)
           this.Add(item);
    }
public void ReportItemChange(T item)
    {
        NotifyCollectionChangedEventArgs args =
        new NotifyCollectionChangedEventArgs(
        NotifyCollectionChangedAction.Replace,
        item,
        item,
        IndexOf(item));
        OnCollectionChanged(args);
    }
}

<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;">&#65279;</span>

This custom observable collection add a method called ReportItemChange that will trigger CollectionEventChanged. So, when we change the visibility property, the UI view will be notified.

Entities

So, for example in this post, I will create list of countries which have list of cities on every country. So, these are entities that I use in this example.


public class City
{
    public string CityName { get; set; }
}

public class Country
{
    public string CountryName { get; set; }
    public bool IsChildrenVisible { get; set; }
    public string ArrowIconSource
    {
        get
        {
           if (IsChildrenVisible)
              return "uparrow.png";
           else
              return "downarrow.png";
        }
    }
    public ObservableCollection Cities { get; set; }
    public int ChildrenRowHeightRequest
    {
        get
        {
            if (Cities != null)
                return Cities.Count * 50;
            else
                return 0;
        }
    }
}

Country, as parent entity, has a visibility property called IsChildrenVisible that will determine whether the cities are showed or not. It also has ChildrenRowHeightRequest property to determine height request for Cities list view.

View Model

After entities, we move on to the view model. In view model we create list of countries that will be bind to the view. As a default, we will set the IsChildrenVisible property to false. And in this view model, we also have method called ShowCities that update that property.


public class AccordionCountriesViewModel : BaseViewModel
{
    private CustomObservableCollection countries;
    public CustomObservableCollection Countries
    {
       get => countries;
       set => SetProperty(ref countries, value);
    }

    public AccordionCountriesViewModel()
    {
       ObservableCollection USACities = new ObservableCollection()
       {
           new City(){ CityName = "Washington DC" },
           new City(){ CityName = "New York" },
           new City(){ CityName = "Los Angeles" }
       };

       ObservableCollection ChinaCities = new ObservableCollection()
       {
           new City(){ CityName = "Beijing" },
           new City(){ CityName = "Shanghai" },
           new City(){ CityName = "Shenzhen" }
       };

       ObservableCollection RussiaCities = new ObservableCollection()
       {
          new City(){ CityName = "Moscow" },
          new City(){ CityName = "St. Peterburg" },
          new City(){ CityName = "Kazan" }
       };

       countries = new CustomObservableCollection()
       {
          new Country(){ CountryName = "USA", IsChildrenVisible = false, Cities = USACities },
          new Country(){ CountryName = "China", IsChildrenVisible = false, Cities = ChinaCities },
          new Country(){ CountryName = "Russia", IsChildrenVisible = false, Cities = RussiaCities },
       };
   }

   public void ShowCities(Country mCountry)
   {
      Country country = Countries.SingleOrDefault(c => c.CountryName == mCountry.CountryName);
      country.IsChildrenVisible = !country.IsChildrenVisible;
      Countries.ReportItemChange(country);
   }
}

Let’s try it out

All preparations are complete. We will create a view which has list view inside another list view. And then we user tap the list view item, it will call method ShowCities in view model.

Screen Shot 2018-02-22 at 6.25.00 AM
And this is how we bind the view model and handle the item tapped event.


public partial class AccordionCountriesPage : ContentPage
{
    AccordionCountriesViewModel viewModel;
    public AccordionCountriesPage()
    {
         InitializeComponent();
         BindingContext = viewModel = new AccordionCountriesViewModel();
    }

    void Handle_ItemTapped(object sender, ItemTappedEventArgs e)
    {
        Country mCountry = (Country)e.Item;
        viewModel.ShowCities(mCountry);
    }
}

Now you can try and run the code.

Credit :

  • Arrow Icons from FlatIcon, Down Arrow icon by Freepik, Right Arrow icon by GraphicsBay, Up Arrow icon by Hadrien

 

UPDATE : Adding Animation to Accordion List View