ListView

ListView is designed to display large amounts of data and, as a result, has scrolling functionality built-in. It allows only single item selection and will automatically highlight the selected item. The selected item is available through the SelectedItem property as an object type. An ItemSelected event executes when the selected item changes.

<?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:HelloXamarinForms"
		x:Class="HelloXamarinForms.MainPageXaml"
		BackgroundColor="#2686DC">

	<ContentPage.Padding>
		<OnPlatform x:TypeArguments="Thickness" iOS="10, 20, 10, 0" Android="10, 0" />
	</ContentPage.Padding>

	<ListView x:Name="listView" ItemSelected="OnItemSelected" SeparatorVisibility="None" />

</ContentPage>

Changing the usernames array will not automatically update the screen.

using Xamarin.Forms;

namespace HelloXamarinForms
{
	public partial class MainPageXaml : ContentPage
	{
		public MainPageXaml()
		{
			InitializeComponent();

			string[] usernames = { "Xavier", "Aaron", "David",
					"Jill", "Andrew", "John",
					"James", "William", "Julie" };

			System.Array.Sort(usernames);

			listView.ItemsSource = usernames;
		}

		void OnItemSelected(object sender, System.EventArgs e)
		{
			if (listView.SelectedItem != null)
			{
				DisplayAlert("OnItemSelected", listView.SelectedItem.ToString(), "OK");
			}
		}
	}
}

ListView can just as easily use List as a source because List and Array both inherit the IEnumerable interface. Changing the List data will not automatically update the screen.

The ItemTapped event is executed when ever the user taps on an item even if it is the item that is already selected.

<?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:HelloXamarinForms"
		x:Class="HelloXamarinForms.MainPageXaml"
		BackgroundColor="#2686DC">

	<ContentPage.Padding>
		<OnPlatform x:TypeArguments="Thickness" iOS="10, 20, 10, 0" Android="10, 0" />
	</ContentPage.Padding>

	<ListView x:Name="listView" ItemTapped="OnItemTapped" SeparatorVisibility="None" />

</ContentPage>
using System.Collections.Generic;
using Xamarin.Forms;

namespace HelloXamarinForms
{
	public partial class MainPageXaml : ContentPage
	{
		public MainPageXaml()
		{
			InitializeComponent();

			List<string> usernames = new List<string>{ "Xavier", "Aaron", "David",
				"Jill", "Andrew", "John",
				"James", "William", "Julie" };

			usernames.Sort();

			listView.ItemsSource = usernames;
		}

		void OnItemTapped(object sender, System.EventArgs e)
		{
			if (listView.SelectedItem != null)
				DisplayAlert("OnItemTapped", listView.SelectedItem.ToString(), "OK");
		}
	}
}

Colors Example

Create a file for a class dealing with colors that will be used in the next few examples.

// Colors.cs
using System.Collections.Generic;
using Xamarin.Forms;

namespace ListViewColors
{
	public class clsColor
	{
		Color color;
		string name, hex, rgb;
		byte a, r, g, b;

		public clsColor(Color Color, string Name)
		{
			this.Color = Color;
			this.Name = Name;
			a = (byte)(Color.A * 255);
			r = (byte)(Color.R * 255);
			g = (byte)(Color.G * 255);
			b = (byte)(Color.B * 255);
		}

		// override ToString() so that it returns the data
		// of the object instead of the class name
		public override string ToString()
		{
			return Name + " " + Hex + " " + Rgb + " " + Color.ToString();
		}

		public Color Color
		{
			get
			{
				return color;
			}
			private set
			{
				color = value;
			}
		}

		public string Name
		{
			get
			{
				return name;
			}
			private set
			{
				name = value;
			}
		}

		// display color info in hexadecimal format
		public string Hex
		{
			get
			{
				if (string.IsNullOrEmpty(hex))
				{
					hex = a.ToString("X2") +
							r.ToString("X2") +
							g.ToString("X2") +
							b.ToString("X2");
				}
				return hex;
			}
		}

		// display color info in RGB format
		public string Rgb
		{
			get
			{
				if (string.IsNullOrEmpty(rgb))
				{
					rgb = r.ToString() +
						", " + g.ToString() +
						", " + b.ToString();
				}
				return rgb;
			}
		}
	}

	public class clsColors
	{
		static public List<clsColor> GetColors
		{
			get
			{
				return new List<clsColor>
					{
						new clsColor(Color.Accent, nameof(Color.Accent)),
						new clsColor(Color.Aqua, nameof(Color.Aqua)),
						new clsColor(Color.Black, nameof(Color.Black)),
						new clsColor(Color.Blue, nameof(Color.Blue)),
						new clsColor(Color.FromRgb(64f/255,64f/255,64f/255), "Dark Gray"),
						new clsColor(Color.Fuchsia, nameof(Color.Fuchsia)),
						new clsColor(Color.Gray, nameof(Color.Gray)),
						new clsColor(Color.Green, nameof(Color.Green)),
						new clsColor(Color.Lime, nameof(Color.Lime)),
						new clsColor(Color.Maroon, nameof(Color.Maroon)),
						new clsColor(Color.Navy, nameof(Color.Navy)),
						new clsColor(Color.Olive, nameof(Color.Olive)),
						new clsColor(Color.Pink, nameof(Color.Pink)),
						new clsColor(Color.Purple, nameof(Color.Purple)),
						new clsColor(Color.Red, nameof(Color.Red)),
						new clsColor(Color.Silver, nameof(Color.Silver)),
						new clsColor(Color.Teal, nameof(Color.Teal)),
						new clsColor(Color.Transparent, nameof(Color.Transparent)),
						new clsColor(Color.White, nameof(Color.White)),
						new clsColor(Color.Yellow, nameof(Color.Yellow))
					};
			}
		}
	}
}

Here is the simpliest example which just uses the overridden ToString() method to display the data of the clsColor object.

<?xml version="1.0" encoding="utf-8" ?>
<!--ListViewColorsPage.xaml-->
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
			 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
			 xmlns:local="clr-namespace:ListViewColors"
			 x:Class="ListViewColors.ListViewColorsPage"
			 BackgroundColor="#2686DC">

	<ContentPage.Padding>
		<OnPlatform x:TypeArguments="Thickness" iOS="10, 20, 10, 0" Android="10, 0" />
	</ContentPage.Padding>

	<StackLayout>
		<ListView ItemsSource="{x:Static local:clsColors.GetColors}" />
	</StackLayout>

</ContentPage>
// ListViewColorsPage.xaml.cs
using Xamarin.Forms;

namespace ListViewColors
{
	public partial class ListViewColorsPage : ContentPage
	{
		public ListViewColorsPage()
		{
			InitializeComponent();
		}
	}
}

Cells

There are five cells: TextCell ImageCell EntryCell SwitchCell ViewCell

ListView displays a list of the same type of cells. TableView displays a list of different cell types.

The ViewCell is a custom cell. If wanting to use a Picker then use a ViewCell. The other cells are self explanatory. Try them all to get a taste for them.

Data Binding

An introduction to data binding is covered in the Xamarin Tutorial.

Still using the clsColor class, modify the ListViewColorsPage.xaml file to use data binding. This example uses the ViewCell which is the most flexible of the cell types. It allows several child elements all bound to properties of the underlying objects.

Notice that the BoxView and three Label are all bound to the properties of clsColor.

<?xml version="1.0" encoding="utf-8" ?>
<!--ListViewColorsPage.xaml-->
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
			 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
			 xmlns:local="clr-namespace:ListViewColors"
			 x:Class="ListViewColors.ListViewColorsPage"
			 BackgroundColor="#2686DC">

	<ContentPage.Padding>
		<OnPlatform x:TypeArguments="Thickness" iOS="10, 20, 10, 0" Android="10, 0" />
	</ContentPage.Padding>
	
	<StackLayout>
		<ListView ItemsSource="{x:Static local:clsColors.GetColors}"
					BackgroundColor="Transparent"
					RowHeight="66">
			<ListView.ItemTemplate>
				<DataTemplate>
					<ViewCell>
						<ContentView Padding="3">
							<Frame OutlineColor="Transparent"
									 Padding="5"
									 BackgroundColor="Transparent">
								<StackLayout Orientation="Horizontal">
									<BoxView Color="{Binding Color}"
											 HeightRequest="50"
											 WidthRequest="50" />
									<StackLayout>
										<Label Text="{Binding Name}"
												 TextColor="Black"
												 FontSize="10"
												 VerticalOptions="StartAndExpand" />
										<Label Text="{Binding Hex, StringFormat='#{0}'}"
												 TextColor="Black"
												 FontSize="8"
												 VerticalOptions="CenterAndExpand" />
										<Label Text="{Binding Rgb, StringFormat='RGB: {0}'}"
												 TextColor="Black"
												 FontSize="8"
												 VerticalOptions="EndAndExpand" />
									</StackLayout>
								</StackLayout>
							</Frame>
						</ContentView>
					</ViewCell>
				</DataTemplate>
			</ListView.ItemTemplate>
		</ListView>
	</StackLayout>

</ContentPage>

MVVM

An introduction to MVVM is covered in the Xamarin Tutorial. The ObservableCollection class, which is covered in the Xamarin tutorial, is required to make this work.

The problem with the previous example is that if a property of the underlying objects changes then the user interface does not reflect it. With MVVM, this is no longer a problem because the properties are linked to the user interface.

Phonebook Example

This example is more "real-world" than the one in the tutorial because it uses Models for the data, however, a model is not required and often times the interface just binds with the ViewModel where business rules are usually enforced. It downloads the data as an XML file over the internet and then deserializes it using XmlSerializer.

To add a TableView allowing the editing of the phonebook entries, see this code.

There are five files listed here:

  1. The XAML file (the View file).
  2. The XAML code-behind file.
  3. The ViewModel file.
  4. The Model file.
  5. The file for the App's main entry point.
<?xml version="1.0" encoding="utf-8" ?>
<!--PhonebookListView.xaml-->
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
			 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
			 xmlns:local="clr-namespace:PhonebookMvvm"
			 x:Class="PhonebookMvvm.PhonebookListView"
			 BackgroundColor="#2686DC">

	<ContentPage.Padding>
		<OnPlatform x:TypeArguments="Thickness" iOS="10, 20, 10, 0" Android="10, 0" />
	</ContentPage.Padding>

	<ContentPage.BindingContext>
		<local:PhonebookViewModel />
	</ContentPage.BindingContext>

	<StackLayout>
		<Label TextColor="#333399"
				 Text="{Binding MainText}"
				 HorizontalTextAlignment="Center" />
		<!--ListView is bound to People, not Phonebook-->
		<ListView ItemsSource="{Binding Phonebook.People}"
					BackgroundColor="Transparent"
					SeperatorColor="Gray">
			<ListView.ItemTemplate>
				<DataTemplate>
					<!--The binding context of ImageCell is Phonebook.People.Person-->
					<ImageCell ImageSource="{Binding ImageUrl}"
									Text="{Binding Name}"
									TextColor="#333399"
									Detail="{Binding Phone}"
									DetailColor="#336699">
						<!--User swipes the item to pull up this context menu-->
						<ImageCell.ContextActions>
							<!--Command is bound to the properties of the Person class-->
							<MenuItem Text="Clear Phone"
										Command="{Binding ClearPhoneCommand}" />
							<MenuItem Text="Remove"
										IsDestructive="True"
										Command="{Binding RemoveCommand}" />
						</ImageCell.ContextActions>
					</ImageCell>
				</DataTemplate>
			</ListView.ItemTemplate>
		</ListView>
	</StackLayout>

</ContentPage>
// PhonebookListView.xaml.cs
using Xamarin.Forms;

namespace PhonebookMvvm
{
	public partial class PhonebookListView : ContentPage
	{
		public PhonebookListView()
		{
			InitializeComponent();
		}
	}
}

This code uses LINQ and WebRequest/WebResponse, see LINQ Tutorial and WebRequest/WebResponse.

// PhonebookViewModel.cs
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Xml.Serialization;
using System.Collections.ObjectModel;
using System.Linq;

namespace PhonebookMvvm
{
	class PhonebookViewModel : INotifyPropertyChanged
	{
		Phonebook phonebook;
		string mainText;

		public PhonebookViewModel()
		{
			Phonebook = new Phonebook();
			Phonebook.People.Add(new Person());

			HttpWebRequest request =
				WebRequest.CreateHttp("http://www.prowaretech.com/_phonebook.xml");

			request.BeginGetResponse((arg) =>
			{
				try
				{
					using (WebResponse response = request.EndGetResponse(arg))
					{
						using (Stream stream = response.GetResponseStream())
						{
							using (StreamReader reader = new StreamReader(stream))
							{
								XmlSerializer xml = new XmlSerializer(typeof(Phonebook));
								Phonebook = xml.Deserialize(reader) as Phonebook;

								// use Linq to sort People (Person collection)
								var query = from pb in Phonebook.People
											orderby pb.LastName, pb.FirstName
											select pb;

								ObservableCollection<Person> people =
									new ObservableCollection<Person>(query);

								Phonebook.People.Clear();
								Phonebook.People = people;

								// set the Phonebook property in each Person
								foreach (Person person in Phonebook.People)
								{
									person.Phonebook = Phonebook;
								}
							}
						}
					}
				}
				catch (System.Exception ex)
				{
					MainText = ex.Message;
				}
			}, null);
		}

		public string MainText
		{
			get
			{
				return mainText;
			}
			set
			{
				if (value == mainText)
					return;
				mainText = value;
				OnPropertyChanged(nameof(MainText));
			}
		}

		public Phonebook Phonebook
		{
			get
			{
				return phonebook;
			}
			private set
			{
				if (value == phonebook)
					return;
				phonebook = value;
				OnPropertyChanged(nameof(Phonebook));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
		void OnPropertyChanged(string propName)
		{
			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
		}
	}
}
// PhonebookModel.cs
using System.ComponentModel;
using System.Xml.Serialization;
using System.Windows.Input;
using System.Collections.ObjectModel;

namespace PhonebookMvvm
{
	public class Phonebook : INotifyPropertyChanged
	{
		ObservableCollection<Person> people = new ObservableCollection<Person>();

		public ObservableCollection<Person> People
		{
			get
			{
				return people;
			}
			set
			{
				if (value == people)
					return;
				people = value;
				OnPropertyChanged(nameof(People));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
		void OnPropertyChanged(string propName)
		{
			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
		}
	}

	public class Person : INotifyPropertyChanged
	{
		string lastName, firstName, phone, imageUrl;

		public Person()
		{
			ClearPhoneCommand = new Xamarin.Forms.Command(() => this.Phone = "");
			RemoveCommand = new Xamarin.Forms.Command(() => Phonebook.People.Remove(this));
		}

		public string ImageUrl
		{
			get
			{
				return imageUrl;
			}
			set
			{
				if (value == imageUrl)
					return;
				imageUrl = value;
				OnPropertyChanged(nameof(ImageUrl));
			}
		}

		[XmlIgnore]
		public string Name
		{
			get
			{
				System.Text.StringBuilder name = new System.Text.StringBuilder(100);
				if(string.IsNullOrEmpty(lastName) && string.IsNullOrEmpty(firstName))
				{
					name.Append("UNDEFINED");
				}
				else if(string.IsNullOrEmpty(lastName))
				{
					name.Append(firstName);
				}
				else if (string.IsNullOrEmpty(firstName))
				{
					name.Append(lastName);
				}
				else
				{
					name.Append(lastName);
					name.Append(", ");
					name.Append(firstName);
				}
				return name.ToString();
			}
		}

		public string LastName
		{
			get
			{
				return lastName;
			}
			set
			{
				if (value == lastName)
					return;
				lastName = value;
				OnPropertyChanged(nameof(LastName));
				OnPropertyChanged(nameof(Name));
			}
		}

		public string FirstName
		{
			get
			{
				return firstName;
			}
			set
			{
				if (value == firstName)
					return;
				firstName = value;
				OnPropertyChanged(nameof(FirstName));
				OnPropertyChanged(nameof(Name));
			}
		}

		public string Phone
		{
			get
			{
				return phone;
			}
			set
			{
				if (value == phone)
					return;
				phone = value;
				OnPropertyChanged(nameof(Phone));
			}
		}

		[XmlIgnore]
		public ICommand ClearPhoneCommand { private set; get; }

		[XmlIgnore]
		public ICommand RemoveCommand { private set; get; }

		[XmlIgnore]
		public Phonebook Phonebook { get; set; }

		public event PropertyChangedEventHandler PropertyChanged;
		void OnPropertyChanged(string propName)
		{
			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
		}
	}
}
// App.cs
using Xamarin.Forms;

namespace PhonebookMvvm
{
	public class App : Application
	{
		public App()
		{
			MainPage = new PhonebookListView();
		}

		protected override void OnStart()
		{
			// Handle when your app starts
		}

		protected override void OnSleep()
		{
			// Handle when your app sleeps
		}

		protected override void OnResume()
		{
			// Handle when your app resumes
		}
	}
}