A ListView is a kind of repeater but isn’t always what I want. It’s surprising something like this isn’t included in the framework but making your own is fairly simple.
namespace PSC.Controls
{
/// <summary>
/// Repeater view.
/// </summary>
public class RepeaterView : StackLayout
{
/// <summary>
/// The item template property.
/// </summary>
public static readonly BindableProperty ItemTemplateProperty =
BindableProperty.Create(
"ItemTemplate",
typeof(DataTemplate),
typeof(RepeaterView),
null,
propertyChanged: (bindable, value, newValue) =>
Populate(bindable));
/// <summary>
/// The items source property.
/// </summary>
public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create(
"ItemsSource",
typeof(IEnumerable),
typeof(RepeaterView),
null,
BindingMode.OneWay,
propertyChanged: (bindable, value, newValue) =>
Populate(bindable));
/// <summary>
/// Gets or sets the items source.
/// </summary>
/// <value>The items source.</value>
public IEnumerable ItemsSource
{
get => (IEnumerable)this.GetValue(ItemsSourceProperty);
set => this.SetValue(ItemsSourceProperty, value);
}
/// <summary>
/// Gets or sets the item template.
/// </summary>
/// <value>The item template.</value>
public DataTemplate ItemTemplate
{
get => (DataTemplate)this.GetValue(ItemTemplateProperty);
set => this.SetValue(ItemTemplateProperty, value);
}
/// <summary>
/// Populate the specified bindable.
/// </summary>
/// <returns>The populate.</returns>
/// <param name="bindable">Bindable.</param>
private static void Populate(BindableObject bindable)
{
var repeater = (RepeaterView)bindable;
// Clean
repeater.Children.Clear();
// Only populate once both properties are received
if (repeater.ItemsSource == null ||
repeater.ItemTemplate == null)
return;
foreach (var viewModel in repeater.ItemsSource)
{
var content = repeater.ItemTemplate.CreateContent();
if (!(content is View) && !(content is ViewCell))
{
throw new Exception(
$"Invalid visual object {nameof(content)}");
}
var view = content is View ? content as View :
((ViewCell)content).View;
view.BindingContext = viewModel;
repeater.Children.Add(view);
}
}
}
}
The view is based on StackLayout which will take care of positioning the items.
There are a couple of bindable properties – ItemTemplate for the item layout and ItemsSource which provides the items. Note that ItemsSource hooks an action to PropertyChanging – I’ll come back to this later on.
Usage in XAML is similar to ListView, with an ItemTemplate, DataTemplate, and ViewCell.
<ui:Repeater x:Name="MainRepeater">
<ui:Repeater.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Title}"
HorizontalOptions="StartAndExpand"
VerticalTextAlignment="Center"/>
<Button Text="Select" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ui:Repeater.ItemTemplate>
</ui:Repeater>
Don’t forget to define ui
<ContentPage xmlns="https://xamarin.com/schemas/2014/forms"
xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ui="clr-namespace:PSC.Controls;assembly=PSC.Controls">