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

Grid Menu with Flow List View

I’m currently working in a project that require me to create a grid menu. In my previous project, I manually created it using grid, but later I found out a more effective way to create grid menu. I’m using third party library, created by the infamous Daniel Luberda, called Flow List View. Unlike the default list view which only has one column, flow list view allow you to set how many columns you want for your list view. In this post I want to discuss about how basically we can utilize this flow list view with MVVM pattern.

Selected Item Handling

When you create a menu for your app, there are, at least, two things you have to handle. First, how it handles user input and second how to navigate to other pages. To handle user input, you can see the following xaml code to get glimpse how it’ll be done.


 
  
   
    
    
   

  
   
  

 
  
   
    
     
      
       
        
        
       

      
      
      
      
      
     
    
   
  
 

  
 

Take a look once again at Flow List View. As you can see it has several properties, two of them are Flow Last Tapped Item and Flow Item Tapped Command. Base on it’s name, you can guess the function of those properties. You can bind the menu item that user just tapped to Flow Last Tapped Item, and then Flow Item Tapped Command will execute the command to handle the tapped event. So, when user tap one of the menus, we can record what menu he just tapped and do action accordingly. Another important property is Flow Column Count, where you set how many column your menu will be.

Navigation Handling

After we’ve done doing binding in xaml file, we move on to the view model. In view model, to navigate to another page, we’ll need INavigation from the class binder. We’ll pass it from constructor and use it to navigate to other page depend on the tapped menu. The following codes are the example how I did it.

 

public class BreakfastViewModel : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;
  private INavigation navigation;
  private ObservableCollection breakfastMenuList;
  private BreakfastMenu selectedBreakfastMenu;
  public ObservableCollection BreakfastMenuList
  {
   get => breakfastMenuList;
   set => SetObservableProperty(ref breakfastMenuList, value);
  }
  public BreakfastMenu SelectedBreakfastMenu
  {
   get => selectedBreakfastMenu;
   set => SetObservableProperty(ref selectedBreakfastMenu, value);
  }

  public ICommand MenuTappedCommand { get; set; }

  public BreakfastViewModel(INavigation navigation)
  {
   this.navigation = navigation;
   BreakfastMenuList = new ObservableCollection()
   {
     new BreakfastMenu() { ImageSource = "Burger.png", MenuTitle = "BURGER" },
     new BreakfastMenu() { ImageSource = "Pizza.png", MenuTitle = "PIZZA" },
     new BreakfastMenu() { ImageSource = "EggBacon.png", MenuTitle = "BACON" },
     new BreakfastMenu() { ImageSource = "Sandwich.png", MenuTitle = "SANDWICH" },
   };
   MenuTappedCommand = new Command(async () => await MenuSelectedAsync());
 }

private async Task MenuSelectedAsync()
{
 switch (SelectedBreakfastMenu.MenuTitle)
 {
  case "BURGER":
   await navigation.PushModalAsync(new BreakfastBurger());
   break;
  case "PIZZA":
   await navigation.PushModalAsync(new BreakfastPizza());
   break;
  case "BACON":
   await navigation.PushModalAsync(new BreakfastBacon());
   break;
  case "SANDWICH":
   await navigation.PushModalAsync(new BreakfastSandwich());
   break;
  }

}

protected void SetObservableProperty(ref T field, T value,
[CallerMemberName] string propertyName = "")
{
  if (EqualityComparer.Default.Equals(field, value)) return;
  field = value;
  OnPropertyChanged(propertyName);
}

protected virtual void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

 

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class BreakfastPage : ContentPage
{
 public BreakfastPage()
 {
   InitializeComponent();
   BindingContext = new BreakfastViewModel(Navigation);
 }
}

If there’s anything you need to pay more attention is, how I navigate. I used PushModalAsync instead of, the more commonly used, PushAsync, because PushAsync is not supported globally to be used in view model.

Credit:

  • Icon from FlatIcon, Burger icon by Freepik, Pizza icon by Smashicons, EggBacon and Sandwich Icon by Twitter
  • Flow List View by Daniel Luberda (github)

Simple Way to bind Selected Item Changed to View Model

As we know together, in Xamarin we can bind command and command parameter to a button. That command will be fired when button is clicked and we can pass parameter through command parameter. But it’s kinda more complicated when it comes to picker or list view. We have selected index changed and item selected in picker and list view respectively to handle the event when user pick a certain item, but we need function in back-end to capture the event before we process it to view model. We also can use behavior to bind the event directly to view model, but it’s bit too complicated for me, especially to do simple task like this. And recently I found a simple workaround to address this problem.

Property Binding

What I do is binding the Selected Item property to certain variable in view model. The binding mode can be one way to source, or two way if you want to initialize the selected item. Continuing from my previous post, I will use picker instead of list view in this example. I will also use the custom picker that I used in previous post. But the trick will also works for list view.

I’ve created two pickers in xaml file and the value that bind to the second picker will be depended to what user choose in first picker.   Here’s how the code look like.

<?xml version="1.0" encoding="utf-8" ?>
 <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:control="clr-namespace:App1.CustomControls" x:Class="App1.Pages.PizzaPage">
  <ContentPage.Content>
   <StackLayout VerticalOptions="Center" Padding="30,0,30,0">
    <Label Text="Choose your Pizza!!!"  TextColor="#118E8C" FontSize="Large"/>
    <control:CustomPicker Title="Select Pizza Country" ItemsSource="{Binding CountryList}" FontSize="48" SelectedItem="{Binding SelectedCountry, Mode=TwoWay}"/>
    <control:CustomPicker Title="Select Pizza Type" ItemsSource="{Binding PizzaList}" FontSize="42"/>
   </StackLayout>
  </ContentPage.Content>
 </ContentPage>

Selected Item Handling

As you can see, in the first picker, Selected Item property is bind to property named Selected Country. In the view model, when this variable is set, I will call function to update the list that is bind to the second picker. So, whenever user change his choice in first picker, the value of second picker will also change simultaneously. The following code is how the view model look like.

public class PizzaViewModel : INotifyPropertyChanged
{
  private ObservableCollection<string> countryList;
  private ObservableCollection<string> pizzaList;
  private string selectedCountry;

  public ObservableCollection<string> CountryList
  {
    get => countryList;
    set
    {
      SetObservableProperty(ref countryList, value);
    }
  }
  public ObservableCollection<string> PizzaList
  {
   get => pizzaList;
   set => SetObservableProperty(ref pizzaList, value);
  }

  public string SelectedCountry
  {
    get => selectedCountry;
    set
    {
      if (value != selectedCountry && value != null)
      LoadPizzaType(value);
      SetObservableProperty(ref selectedCountry, value);
    }
}

public event PropertyChangedEventHandler PropertyChanged;

public PizzaViewModel()
{
  LoadPizzaCountry();
}

private async void LoadPizzaCountry()
{
  CountryList = await PizzaServices.GetPizzaCountry();
}

private async void LoadPizzaType(string country)
{
  PizzaList = await PizzaServices.GetPizzaType(country);
}

protected void SetObservableProperty<T>(ref T field, T value,
[CallerMemberName] string propertyName = "")
{
  if (EqualityComparer<T>.Default.Equals(field, value)) return;
  field = value;
  OnPropertyChanged(propertyName);
}

protected virtual void OnPropertyChanged(string propertyName)
 => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Take a look at variable Selected Country, it’s where the trick lies. With this simple trick, you can bind something similar like command and command parameter in one packet to picker and list view.