Repeater or Bindable StackLayout


When designing a View (Page) we need to take into consideration that there might be a lot of content to show. Typically we should use a ListView, which by default is scrollable. However, what if you have to show more than one ListView on a single page? Nesting ScrollViews is a very bad practice that should be avoided unless natively supported. In this case it will most probably make sense to put all the content within a single ScrollView. But how? Here is where the Repeater or BindableStackLayout comes into play.

Custom Control

In order to solve the problem described in the Intro, we will have to extend a StackLayout and expose the next bindable properties:

  • ItemsSource – Gets/sets the source of items to template and display.
  • ItemDataTemplate – Gets/sets the item template.
  • Title – Gets/sets the header/title of the control.

The implementation is very simple and does not require any custom renderers since we are simply going to reuse a layout that arranges child views vertically or horizontally. We are going to use BindableProperties in order to be able to react to value changes in a real time.

Here is a very simple implementation in ±50 lines of code:

public class BindableStackLayout : StackLayout
    readonly Label header;

    public BindableStackLayout()
        header = new Label();

    public IEnumerable ItemsSource
        get { return (IEnumerable)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    public static readonly BindableProperty ItemsSourceProperty =
        BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(BindableStackLayout),
                                propertyChanged: (bindable, oldValue, newValue) => ((BindableStackLayout)bindable).PopulateItems());

    public DataTemplate ItemDataTemplate
        get { return (DataTemplate)GetValue(ItemDataTemplateProperty); }
        set { SetValue(ItemDataTemplateProperty, value); }
    public static readonly BindableProperty ItemDataTemplateProperty =
        BindableProperty.Create(nameof(ItemDataTemplate), typeof(DataTemplate), typeof(BindableStackLayout));

    public string Title
        get { return (string)GetValue(TitleProperty); }
        set { SetValue(TitleProperty, value); }
    public static readonly BindableProperty TitleProperty =
        BindableProperty.Create(nameof(Title), typeof(string), typeof(BindableStackLayout),
                                propertyChanged: (bindable, oldValue, newValue) => ((BindableStackLayout)bindable).PopulateHeader());

    void PopulateItems()
        if (ItemsSource == null) return;
        foreach (var item in ItemsSource)
            var itemTemplate = ItemDataTemplate.CreateContent() as View;
            itemTemplate.BindingContext = item;

    void PopulateHeader() => header.Text = Title;

All the “stuff” is happening within the PopulateItems method which is called when a value of ItemSource property is changing. We simply iterate through the collection of items and add them as children to the root view. Please note that each child is represented by ItemDataTemplate, so all we have to do, is to invoke CreateContent method that will be generate the view for us.

Usage example:

public sealed class MyColor
    public string Name { get; }
    public string HexCode { get; }

    public MyColor(string name, string hexCode)
        Name = name;
        HexCode = hexCode;

public class MainViewModel
    public IReadOnlyCollection<MyColor> MyColors { get; } = new List<MyColor>
        new MyColor(“Black”, “#000000”),
        new MyColor(“Hot Pink”, “#ff69b4”),
        new MyColor(“Red”, “#ff0000”),
        new MyColor(“Sort of Green”, “#7cff00”)

<?xml version=1.0 encoding=utf-8?>
        <local:MainViewModel />
        ItemsSource={Binding MyColors}
                        Color={Binding HexCode} />
                        Text={Binding Name}
                        VerticalTextAlignment=Center />

The result:


The implementation above is very simple, however some corner cases are not handled on purpose. This can be a nice homework for you to discover and handle those. The code above is fully available on github.

Good luck!

3 thoughts on “Repeater or Bindable StackLayout

  1. I have added SelectedItemChanged event to the original code. This event can be used to get the selected item.

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Input;
    using Xamarin.Forms;

    namespace applause
    public class BindableStackLayout : StackLayout
    readonly Label header;
    public BindableStackLayout()
    header = new Label();

    ItemSelectedCommand = new Command(item =>
    SelectedItem = item;

    public event EventHandler SelectedItemChanged;

    public IEnumerable ItemsSource
    get { return (IEnumerable)GetValue(ItemsSourceProperty); }
    set { SetValue(ItemsSourceProperty, value); }
    public static readonly BindableProperty ItemsSourceProperty =
    BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(BindableStackLayout),
    propertyChanged: (bindable, oldValue, newValue) => ((BindableStackLayout)bindable).PopulateItems());

    public DataTemplate ItemDataTemplate
    get { return (DataTemplate)GetValue(ItemDataTemplateProperty); }
    set { SetValue(ItemDataTemplateProperty, value); }
    public static readonly BindableProperty ItemDataTemplateProperty =
    BindableProperty.Create(nameof(ItemDataTemplate), typeof(DataTemplate), typeof(BindableStackLayout));

    public string Title
    get { return (string)GetValue(TitleProperty); }
    set { SetValue(TitleProperty, value); }
    public static readonly BindableProperty TitleProperty =
    BindableProperty.Create(nameof(Title), typeof(string), typeof(BindableStackLayout),
    propertyChanged: (bindable, oldValue, newValue) => ((BindableStackLayout)bindable).PopulateHeader());

    public object SelectedItem
    get { return GetValue(SelectedItemProperty); }
    set { SetValue(SelectedItemProperty, value); }
    public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create(p => p.SelectedItem, default(object), BindingMode.TwoWay, null, OnSelectedItemChanged);

    private static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
    var itemsView = (BindableStackLayout)bindable;
    if (newValue == oldValue)


    protected virtual void SetSelectedItem(object selectedItem)
    var handler = SelectedItemChanged;
    if (handler != null)
    handler(this, new SelectedItemChangedEventArgs(selectedItem));

    void PopulateItems()
    if (ItemsSource == null) return;
    foreach (var item in ItemsSource)

    void PopulateHeader() => header.Text = Title;

    protected virtual View GetItemView(object item)
    var content = ItemDataTemplate.CreateContent();

    var view = content as View;
    if (view == null)
    return null;

    view.BindingContext = item;

    var gesture = new TapGestureRecognizer
    Command = ItemSelectedCommand,
    CommandParameter = item

    AddGesture(view, gesture);

    return view;

    protected readonly ICommand ItemSelectedCommand;

    protected void AddGesture(View view, TapGestureRecognizer gesture)

    var layout = view as Layout;

    if (layout == null)

    foreach (var child in layout.Children)
    AddGesture(child, gesture);


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s