Xamarin Forms - Change a ListView's Selected Item Colour

April 11, 2017

I recently had to set the colour for a Xamarin Forms ListView’s SelectedItem to something other than the default. This was primarily because the colour scheme for the app I was building on iOS didn’t play nice with the selected-item highlight when it was tapped.

This was actually a little bit more work than I thought it would be (since there’s no ‘SelectedItemColor’ property).

ListView items are based on Cell-derived classes like ViewCell and TextCell, and so it’s not actually the ListView’s responsibility to handle the colour change. It’s up to the Cell-based object to do that itself. I guess they could have made it a little easier by creating a ‘SelectedBackgroundColour’ (or similar) property. But given how simple it is to implement, it’s actually not that big a deal.

The important thing to take away from this is that it’s NOT the ListView you’re modifying, it’s the Cell-based object (embedded in the ListView item’s DataTemplate) which needs to change. The ListView will happily pass on the fact that something has been (un)selected, but leaves it up to the developer to handle what happens when it is.

There was a quick and dirty Android-only solution, which was to set a couple of styles in the Android Resources folder, but (a) this was an Android-only solution (and I need it to work on iOS as well), and (b) by setting the style of pressed and highlighted items, it changed it for ALL controls which used that style. Suddenly ALL my (Android) Xamarin Forms ListViews had the same selected colour, and everything else I touched, tapped or long-pressed had that colour too.

There’s a couple of other examples in that thread which show how to react to the ItemTapped and/or ItemSelected events. But they struck me as a little clunky, mainly because they’d have to be done manually and individually for every ListView.

However, a little bit more digging through that thread led me to an example using a custom renderer and a CustomTextCell.

Since all I needed to do was change the background colour of the ViewCell in my ListView when it was selected, and then reset it back once it was deselected, it was pretty painless to port it over.

Here’s the code:

Original ListView Item DataTemplate

Before changes, it looked something like this (bog-standard ViewCell, nothing special. However the ListView it’s contained in had to have a background colour of Black because reasons):

<ListView.ItemTemplate>
      <DataTemplate>
        <ViewCell>
          <ViewCell.View>
            <StackLayout HorizontalOptions="FillAndExpand" 
                         VerticalOptions="FillAndExpand" 
                         Orientation="Vertical" 
                         Padding="4" Spacing="8">
              <Label TextColor="White" Text="{Binding .ItemName}"/>
              <Label TextColor="Yellow" Text="{Binding .LastUpdated, StringFormat='Last seen: {0:HH:mm:ss}'}"/>
            </StackLayout>
          </ViewCell.View>
        </ViewCell>
      </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Note that on iOS, you can’t tell when an item is selected…

iOS - before implementation

On Android, you can, but the shocking orange default colour is a bit offensive…

Android - before implementation

Let’s fix it with a custom renderer.

Custom ViewCell XAML ‘placeholder’ control

This guy just inherits directly from a ViewCell and adds a single BindableProperty so we can hook up the colour we’d like to use when the item is selected.

using Xamarin.Forms;

namespace xamformsdemo.CustomControls
{
  public class ExtendedViewCell : ViewCell
  {
    public static readonly BindableProperty SelectedBackgroundColorProperty =
        BindableProperty.Create("SelectedBackgroundColor", 
                                typeof(Color), 
                                typeof(ExtendedViewCell), 
                                Color.Default);

    public Color SelectedBackgroundColor
    {
      get { return (Color)GetValue(SelectedBackgroundColorProperty); }
      set { SetValue(SelectedBackgroundColorProperty, value); }
    }
  }
}

Android custom renderer

As the cell is rendered, this one captures the current (unselected, default theme) colour of the Android view cell, and then sets the colour of the view-cell’s background whenever the IsSelected property of the Android view-cell changes from false to true. And then resets it to the originally captured colour when when it flips back.

Obviously this may get a little more complex if you need to select multiple items in your list, but this is as far as I needed to go for my solution.

[assembly: ExportRenderer(typeof(ExtendedViewCell), typeof(ExtendedViewCellRenderer))]
namespace xamformsdemo.Droid.CustomRenderers
{
  public class ExtendedViewCellRenderer : ViewCellRenderer
  {

    private Android.Views.View _cellCore;
    private Drawable _unselectedBackground;
    private bool _selected;

    protected override Android.Views.View GetCellCore(Cell item, 
                                                      Android.Views.View convertView, 
                                                      ViewGroup parent, 
                                                      Context context)
    {
      _cellCore = base.GetCellCore(item, convertView, parent, context);

      _selected = false;
      _unselectedBackground = _cellCore.Background;

      return _cellCore;
    }

    protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
      base.OnCellPropertyChanged(sender, args);

      if (args.PropertyName == "IsSelected")
      {
        _selected = !_selected;

        if (_selected)
        {
          var extendedViewCell = sender as ExtendedViewCell;
          _cellCore.SetBackgroundColor(extendedViewCell.SelectedBackgroundColor.ToAndroid());
        }
        else
        {
          _cellCore.SetBackground(_unselectedBackground);
        }
      }
    }
  }

}

Note that this set/reset code will run every time you (de)select and item in the list.

iOS custom renderer

It doesn’t get much simpler than this one. The cool thing about the iOS implementation is that it sets the selected colour as a property of the cell as it’s being constructed. Once it’s done, the UI doesn’t need to come back to ‘ask’ the custom renderer what colour it should change to (unlike the Android one, above).

[assembly: ExportRenderer(typeof(ExtendedViewCell), typeof(ExtendedViewCellRenderer))]
namespace xamformsdemo.iOS.CustomRenderers
{
  public class ExtendedViewCellRenderer : ViewCellRenderer
  {
    public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
    {
      var cell = base.GetCell(item, reusableCell, tv);
      var view = item as ExtendedViewCell;
      cell.SelectedBackgroundView = new UIView
      {
        BackgroundColor = view.SelectedBackgroundColor.ToUIColor(),
      };

      return cell;
    }

  }
}
  

After dropping in the ExtendedViewCell to replace the original ViewCell, and setting the SelectedBackgroundColor to Teal:

<ListView.ItemTemplate>
      <DataTemplate>
        <customControls:ExtendedViewCell SelectedBackgroundColor="Teal">
          <ViewCell.View>
            <StackLayout HorizontalOptions="FillAndExpand" 
                         VerticalOptions="FillAndExpand" Orientation="Vertical" 
                         Padding="4" Spacing="8">
              <Label TextColor="White" Text="{Binding .ItemName}"/>
              <Label TextColor="Yellow" Text="{Binding .LastUpdated, StringFormat='Last seen: {0:HH:mm:ss}'}"/>
            </StackLayout>
          </ViewCell.View>
        </customControls:ExtendedViewCell>
      </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

How does it look now?

iOS

iOS - after implementation

apologies for the janky iOS screen caps, I was using the iOS simulator over a very poor, long distance wifi connection

Android

Android - after implementation

There’s a simple Xamarin Forms project over in GitHub so you can try it out for yourself. As usual it’s published under the MIT licence, so you can do what you like with the code.


comments powered by Disqus