Adding Animation to Accordion ListView

So, I just started learning about animation in Xamarin. I read the documentation and want to try implementing it. Because I’m new at this stuff, I wanna start with something simple. I modified one of my old post about accordion list view by adding little animation to it. If you’ve read that post, you will know that when I expand the list view, I changed the down arrow icon to up arrow icon. And when I collapse the list view, I do the otherwise. The icon changing process just happen as it is, without any effect or animation on it. I want to change that. I want to add a rotate animation when the icon changing happen. So, let’s do that.

Image Gesture Recognizer

First thing we need to is switching the trigger of expanding list view event. Back then I used List View Item Tapped event, but now, because I want to rotate the arrow icon, I need to put the trigger in Image view where the arrow icon is. So, I add Image Gesture Recognizer to the Image view. It’s look like this.

animated accordion

With this gesture recognizer, I can get the current Image view being tapped by user so I can handle it properly in code behind. In code behind, all I need to is just rotate the image and call function in view model to expand or collapse the list view. I rotate the image 180 degree and for 0,5 second.

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);
        ListView listView = sender as ListView;
        listView.SelectedItem = null;
    }

    async void Handle_Tapped(object sender, EventArgs e)
    {
        Image image = sender as Image;
        await image.RotateTo(180, 500);
        Grid grid = image.Parent as Grid;
        Label label = grid.Children[0] as Label;
        viewModel.ShowCities(label.Text);
    }
}

A little bit downside from this method is I don’t get the whole Country class like I did when I was using List View Item Tapped. But I’m still able to know which country user tapped by accessing the label which contain the country’s name. By doing this, it means I also need little modification to the view model as well. This is how the view model look like now. I only updated the ShowCities method.

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(string countryName)
    {
        Country country = Countries.SingleOrDefault(c => c.CountryName == countryName);
        country.IsChildrenVisible = !country.IsChildrenVisible;
        Countries.ReportItemChange(country);
    }
}

Let’s rotate it!

Nah, now we can run the project again and see how our arrow rotating.

 

Credit :

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

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