Changing ListView Selection color with a Custom Renderer
I don't know about you, but the selected element color of a ListView, when rendered on an Android device, can feel like too much, not to mention just off with the other colors. I don't mind the light gray color for the same behavior when rendered on iOS, but on Android, it just feels wrong.
Take this Listview for instance. It is a simple ListView, with a TextCell as the DataTemplate for the ItemTemplate. Notice the awful color that Android sets as the background for the selected cell. Google knows where did this color come from because as we can see for the rest of the UI, it is not the primary color, nor the accent color! Moreover, I am super confident of this because I updated the colors.xml file myself:
<resources> <color name="launcher_background">#FFFFFF</color> <color name="colorPrimary">#3F51B5</color> <color name="colorPrimaryDark">#303F9F</color> <color name="colorAccent">#FF4081</color> </resources>
So, while I have to expect that there must be a more direct way in which to change this, I couldn't figure it out in the few minutes I spent researching this. Also, since I was already implementing a Custom Renderer to the View and TextCells, I figured there must be a way to change this awful background color from there at least.
And so I did. Although it wasn't as straight forward as I would've hoped, it does work.
First, of course, I had to make sure that I had a custom renderer for the TextCell that I was using, so if you are not familiar with custom renderers, I suggest you take a quick look at one of my previous posts where I explain them in more detail.
By the way, I am showcasing my code on a TextCell in this post, but the code will work as-is on a ViewCell, SwitchCell, ImageCell, and any other Cell you may have as well.
So I have my CustomTextCellRenderer class that inherits from TextCellRenderer. This way I have access to the virtual GetCellCode method, and can, of course, override it. In this override is where you usually add the code that changes the cell, since it is the one that constructs it, but we don't immediately want to change the cell, we want to change it only once it is selected. So we use this method to get the cell itself and assign it to a global variable that we'll have access to from the other method that we will use. Your code then would look like this:
public class CustomTextCellRenderer : TextCellRenderer { private Android.Views.View _cell; private bool _isSelected; protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView, ViewGroup parent, Context context) { _cell = base.GetCellCore(item, convertView, parent, context); _isSelected = false; return _cell; } }
Notice that I'm also defining a global boolean variable, this holds the current selected-state of a cell, assuming that when first displayed, it is unselected.
Now, we have to change the background of the cell when it is selected. We can know whether the IsSelected property of a cell has changed with the help of the OnCellPropertyChanged virtual method that we also have available. We need to evaluate whether the property that changed is indeed the IsSelected property and if that is the case, we inverse the value of our _isSelected variable that we defined earlier:
protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e) { base.OnCellPropertyChanged(sender, e); if (e.PropertyName == "IsSelected") { _isSelected = !_isSelected; } }
From here, it is a quick evaluation of the value contained in our _isSelected boolean. If the value is true, we set the background color to whatever selected color we want. If it is false, we change it back to transparent.
if(e.PropertyName == "IsSelected") { _isSelected = !_isSelected; if (_isSelected) { _cell.SetBackgroundColor(Color.FromHex("#E6E6E6").ToAndroid()); } else { _cell.SetBackgroundColor(Android.Graphics.Color.Transparent); } }
NOTE: Alternatively, you could get the default background value of the cell that we get from the GetCellCore method and set the background back to that value. I guess this is a more accurate way of doing things instead of setting it to transparent, in case the default color wasn't transparent. This could be done like this:
private Android.Views.View _cell; private Drawable _normalBackground; // add this line private bool _isSelected; protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView, ViewGroup parent, Context context) { _cell = base.GetCellCore(item, convertView, parent, context); _normalBackground = _cell.Background; // add this line _isSelected = false; return _cell; } protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e) { base.OnCellPropertyChanged(sender, e); if (e.PropertyName == "IsSelected") { _isSelected = !_isSelected; if (_isSelected) { _cell.SetBackgroundColor(Color.FromHex("#E6E6E6").ToAndroid()); } else { _cell.Background = _normalBackground; // change this line } } }
The result is now much better.
I assumed here that you are at least a bit familiar with custom renderers, but if not, don't forget to export your renderer like this:
[assembly: ExportRenderer(typeof(TextCell), typeof(CustomTextCellRenderer))]
As an additional setup, you could add some evaluations when setting the background, so you have more control over it when defining the cell from your XAML files. Setting otherwise-unused-variables from XAML and evaluating their value from a Custom Renderer can be quite useful. For example, let's say that we set the StyleId from XAML to a color (a string really, but you get the point). Then, from the Custom Renderer, we could evaluate this value and act accordingly:
if (_isSelected) { switch (cell.StyleId) { case "blue": _cell.SetBackgroundColor(Android.Graphics.Color.DodgerBlue); break; case "red": _cell.SetBackgroundColor(Android.Graphics.Color.DarkRed); break; case "gray": _cell.SetBackgroundColor(Xamarin.Forms.Color.FromHex("#E6E6E6").ToAndroid()); break; case "transparent": _cell.SetBackgroundColor(Android.Graphics.Color.Transparent); break; case "green": _cell.SetBackgroundColor(Android.Graphics.Color.PaleGreen); break; default: _cell.SetBackgroundColor(Android.Graphics.Color.LightGray); break; } }