Saturday 17 March 2018

Multiselect list view using Xamarin Forms

Hi all,

Here in this post I'm going to demonstrate how to create a multi select listview in Xamarin Forms, designed for both iOS and Android.

The Listview comprises of an image, textview and a check box icon. Allowing the user to select multiple options. At the end of the listview there is a button used to save the selection.

Since there is no control as CHECKBOX offered by XAML, I used an Image and I rendered the checked and un-checked images on click of the image view. However you can even use custom renders to design a checkbox for both iOS and Android.

So that's said, lets start cooking  : I'm using Visual Studio for MAC

First create an Xamarin Forms project :


After creating the project, the folder structure should look like below :


In the above you can see, three folders are created :
1. PCL (MultiselectListView) 
2. Android (MultiselectListView.Droid)
3. iOS (MultiselectListView.iOS)

The UI and the backend logic of the forms application goes into PCL project making it deployable on both platforms.

The iOS and Android project here have the native bindings required to deploy the app into appropriate platforms.

In this project we are only adding the image required for the app into iOS and Android project and rest all our code will reside in the PCL project.

Now we will create the UI for the listview. Add the following code to MultiselectListViewPage.xaml


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:local="clr-namespace:MultiselectListView" x:Class="MultiselectListView.MultiselectListViewPage">

    <ContentPage.Content>

        <StackLayout Orientation="Vertical" Spacing="20">

            <Label x:Name="HeaderLabel" 

                   Margin="14,17,20,0" 

                   HeightRequest="54"

                   WidthRequest="350"

                   HorizontalTextAlignment="Center" 

                   Text="This is a custom ListView!!" 

                   FontSize="21"

                   TextColor="Black"

                   HorizontalOptions="CenterAndExpand" />

            <StackLayout x:Name="ListViewLayout" 

                   Spacing="10" 

                   Orientation="Vertical" 

                   VerticalOptions="FillAndExpand" 

                   HorizontalOptions="FillAndExpand">

                <ListView x:Name="MultiSelectListView" SeparatorVisibility="None" 

                          Margin="0"

                          RowHeight="55"

                          ItemSelected="OnItemSelected"

                          ItemTapped="OnItemTapped"

                          BackgroundColor="Transparent"

                          HasUnevenRows="True">

                    <ListView.ItemTemplate>

                        <DataTemplate>

                            <ViewCell>

                                <Frame WidthRequest="327" Padding="2" CornerRadius="07" HasShadow="false" BackgroundColor="Silver" Margin="24,10,34,0" HorizontalOptions="CenterAndExpand" VerticalOptions="StartAndExpand">

                                    <Frame WidthRequest="327" Padding="0" CornerRadius="07" HasShadow="false" OutlineColor="Transparent">

                                        <StackLayout WidthRequest="327" Orientation="Horizontal" Spacing="0">

                                            <Image x:Name="ItemImage" HeightRequest="30" WidthRequest="32" Source="{Binding ItemImage}" Margin="58,0,0,0" HorizontalOptions="Start" />

                                            <Label x:Name="ItemName" HeightRequest="30" WidthRequest="106" FontSize="20" Margin="22,0,0,0" Text="{Binding ItemName}" TextColor="Gray"  VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" />

                                            <Image x:Name="ItemCheckbox" HeightRequest="27" WidthRequest="31" Source="{Binding CheckboxImage}" Margin="24,0,0,0" VerticalOptions="CenterAndExpand" HorizontalOptions="EndAndExpand"></Image>

                                        </StackLayout>

                                    </Frame>

                                </Frame>

                            </ViewCell>

                        </DataTemplate>

                    </ListView.ItemTemplate>

                </ListView>

                <StackLayout>

                    <Button Text="Done" TextColor="White" HeightRequest="62" WidthRequest="201" Clicked="SelectItems" VerticalOptions="EndAndExpand" HorizontalOptions="CenterAndExpand" BackgroundColor="#3DBDA0" Margin="0,0,0,62"/>

                </StackLayout>

            </StackLayout>

        </StackLayout>

    </ContentPage.Content>

</ContentPage>

In the above code, we have created the ListView and there we defined "ViewCell" with three elements :
1. Image
2. Text
3. Checkbox

We have also bound these elements with our Model class object using "{Binding" property.

Also we have defined the two events for ListView OnItemSelected and OnItemTapped 

Now lets create our Model class. Create an empty class and name it as ListClass as shown below :


Add the following code :

using System;
namespace MultiselectListView
{
    public class ListItem
    {
        
        //  Item Name.       
        public string ItemName { get; set; }
 
        //  Item Image.
        public string ItemImage { get; set; }

        //  Selection Image.
        public string CheckboxImage { get; set; }
    }
}

Please make sure the name given in the "{Binding" should exactly be same as the Model class attribute name.

Now we have to write our logic in the MultiselectListViewPage.cs class. This is a generated class attached to MultiselectListViewPage.xaml

Our MultiselectListViewPage.cs will look like below :


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Xamarin.Forms;

namespace MultiselectListView
{
    public partial class MultiselectListViewPage : ContentPage
    {
        
        private readonly ObservableCollection<ListItem> multiSelectListItems;

        /// <summary>
        ///     Object used to identify if the user tappep on a selected cell.
        /// </summary>
        private bool _isSelectedItemTap;

        /// <summary>
        ///     Object holding the reference of current user selection.
        /// </summary>
        private int _selectedItemIndex;

        private IList<ListItem> selectedItems = new List<ListItem>();


        public MultiselectListViewPage()
        {
            InitializeComponent();
            BindingContext = this;
            multiSelectListItems = new ObservableCollection<ListItem>();
            multiSelectListItems.Add(new ListItem()
            {
                CheckboxImage = "icons8_unchecked_checkbox.png",
                ItemImage = "icons8_level_1",
                ItemName = "Item1"
                    

            });

            multiSelectListItems.Add(new ListItem()
            {
                CheckboxImage = "icons8_unchecked_checkbox.png",
                ItemImage = "icons8_level_1",
                ItemName = "Item2"


            });

            multiSelectListItems.Add(new ListItem()
            {
                CheckboxImage = "icons8_unchecked_checkbox.png",
                ItemImage = "icons8_level_1",
                ItemName = "Item3"


            });

            multiSelectListItems.Add(new ListItem()
            {
                CheckboxImage = "icons8_unchecked_checkbox.png",
                ItemImage = "icons8_level_1",
                ItemName = "Item4"


            });

            multiSelectListItems.Add(new ListItem()
            {
                CheckboxImage = "icons8_unchecked_checkbox.png",
                ItemImage = "icons8_level_1",
                ItemName = "Item5"


            });

            MultiSelectListView.ItemsSource = multiSelectListItems;
        }

        /// <summary>
        ///     Method called when an item in the Listview is selected.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="args">Args.</param>
        private void OnItemSelected(object sender, SelectedItemChangedEventArgs args)
        {
            _isSelectedItemTap = true;
            _selectedItemIndex = multiSelectListItems.IndexOf((ListItem)args.SelectedItem);
            multiSelectListItems[_selectedItemIndex].CheckboxImage = multiSelectListItems[_selectedItemIndex].CheckboxImage
                .Equals("icons8_unchecked_checkbox.png")
                ? "icons8_checked_checkbox.png"
                : "icons8_unchecked_checkbox.png";
            
        }

        /// <summary>
        ///     Method called when an item in the Listview is tapped.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="itemTappedEventArgs">Args.</param>
        private void OnItemTapped(object sender, ItemTappedEventArgs itemTappedEventArgs)
        {
            if (!_isSelectedItemTap && null != itemTappedEventArgs.Item)
            {
                multiSelectListItems.IndexOf((ListItem)itemTappedEventArgs.Item);
                multiSelectListItems[_selectedItemIndex].CheckboxImage = multiSelectListItems[_selectedItemIndex].CheckboxImage
                    .Equals("icons8_unchecked_checkbox.png")
                    ? "icons8_checked_checkbox.png"
                    : "icons8_unchecked_checkbox.png";
                
            }

            MultiSelectListView.ItemsSource = null;
            MultiSelectListView.ItemsSource = multiSelectListItems;
            _isSelectedItemTap = false;

        }

        /// <summary>
        ///     Method used to identify and configure the selected program as Tinnitus programs.
        /// </summary>
        /// <param name="sender">Sender</param>
        /// <param name="eventArgs">Event args</param>
        private void SelectItems(object sender, EventArgs eventArgs)
        {
            selectedItems = new List<ListItem>();
            string message = "";
            for (int index = 0; index < multiSelectListItems.Count; index++)
            {
                // The selected items will have the active image.
                // This is the paramter used to identify the selected programs to be configured as Tinnitus.
                if (multiSelectListItems[index].CheckboxImage.Equals("icons8_checked_checkbox.png"))
                {
                    selectedItems.Add(multiSelectListItems[index]);
                    message += index+" ";
                }
            }

            DisplayAlert("Items Selected", message,"OK");

            //Navigate to next page.
            //Navigation.PushAsync(new NextPage());
        }
    }
}




In the above class I have used two methods OnItemTapped and OnItemSelected.

If only OnItemSelected is used, then clicking on the selected row will just highlight and "OnItemSelected" method is not called/invoked again.

This is the reason I've added "OnItemTapped" also. By doing so, whenever user clicks on any unselected listview row, both the methods get called and when the user clicks on an already selected row, only "OnItemTapped" is called.

Inside "OnItemTapped" I've used my logic with help of class level bool flags to determine user action and handle the selection of user and update UI accordingly.

I've added a button "DONE" at the end of listview. After selecting the items, clicking on the "DONE" button displays an alert with the selected index.

That's it!!! Now time to play with the app.

Refer below screenshots to see how the app looks on both platforms :

Android :



iOS :





Please note : 

To update the ListView I'm resetting the itemsource of ListView. However you can use the INotifyPropertyChanged in your model class and try updating the ListView.

ObservableCollectionChanged is not an ideal callback to use here. This is because, it is only called on adding new items to collection and not updating the current items in the collection.

That's all guys!!! Hope this tutorial helps you in understanding the ListView behavior in Forms.

4 comments:

  1. Hi, nice tutorial
    Please can you send me icons8_unchecked_checkbox.png

    ReplyDelete
    Replies
    1. Thank you!! Please find the icons here : https://icons8.com/icons/set/unchecked and https://icons8.com/icons/set/checked

      Delete
  2. thank you so much, I was stuff from one week.

    ReplyDelete