Xamarin.Forms ListView: Set the highlight color of a tapped item
Asked Answered
W

16

86

Using Xamarin.Forms, how can I define the highlight/background color of a selected/tapped ListView item?

(My list has a black background and white text color, so the default highlight color on iOS is too bright. In contrast, on Android there is no highlighting at all - up to a subtle horizontal gray line.)

Example: (left: iOS, right: Android; while pressing "Barn2")

Waybill answered 17/9, 2014 at 7:59 Comment(1)
These days, Change the background color of a selected item using VisualStateManager is an alternative. That article says it works with ListView also.Treytri
W
36

iOS

Solution:

Within a custom ViewCellRenderer you can set the SelectedBackgroundView. Simply create a new UIView with a background color of your choice and you're set.

public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
    var cell =  base.GetCell(item, reusableCell, tv);

    cell.SelectedBackgroundView = new UIView {
        BackgroundColor = UIColor.DarkGray,
    };

    return cell;
}

Result:

Note:

With Xamarin.Forms it seems to be important to create a new UIView rather than just setting the background color of the current one.


Android

Solution:

The solution I found on Android is a bit more complicated:

  1. Create a new drawable ViewCellBackground.xml within the Resources>drawable folder:

    <?xml version="1.0" encoding="UTF-8" ?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_pressed="true" >
            <shape android:shape="rectangle">
                <solid android:color="#333333" />
            </shape>
        </item>
        <item>
            <shape android:shape="rectangle">
                <solid android:color="#000000" />
            </shape>
        </item>
    </selector>
    

    It defines solid shapes with different colors for the default state and the "pressed" state of a UI element.

  2. Use a inherited class for the View of your ViewCell, e.g.:

    public class TouchableStackLayout: StackLayout
    {
    }
    
  3. Implement a custom renderer for this class setting the background resource:

    public class ElementRenderer: VisualElementRenderer<Xamarin.Forms.View>
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
        {
            SetBackgroundResource(Resource.Drawable.ViewCellBackground);
    
            base.OnElementChanged(e);
        }
    }
    

Result:

Waybill answered 17/9, 2014 at 9:58 Comment(5)
If setting the background color doesn't work for the existing UIView try calling the cell's SetNeedsLayout (or whatever that method was called) after setting its background color. UITableView tries to not perform layout whenever that's possibleBloodroot
@StenPetrov: Nope, SetNeedsDisplay or ``` SetNeedsLayout``` does not work. But it doesn't really matter, since assigning new UIView {...} is a pretty short workaround.Waybill
And how to change the font color?Bayle
On Android the selected color isn't retained for me. I've tried using android:state_selected.. :[Clerc
SelectedBackgroundView is for selection, not highlightning. Question is about highlightningPierre
R
133

In Android simply edit your styles.xml file under Resources\values adding this:

<resources>
    <style name="MyTheme" parent="android:style/Theme.Material.Light.DarkActionBar">
        <item name="android:colorPressedHighlight">@color/ListViewSelected</item>
        <item name="android:colorLongPressedHighlight">@color/ListViewHighlighted</item>
        <item name="android:colorFocusedHighlight">@color/ListViewSelected</item>
        <item name="android:colorActivatedHighlight">@color/ListViewSelected</item>
        <item name="android:activatedBackgroundIndicator">@color/ListViewSelected</item>
    </style>
    <color name="ListViewSelected">#96BCE3</color>
    <color name="ListViewHighlighted">#E39696</color>
</resources>
Rajkot answered 19/7, 2016 at 11:56 Comment(5)
the issue is only happening on Android, so this is the best/cleanest solution for meLambdacism
Easiest solution for me too. It's such a pity Xamarin Forms don't have a customizable property for that.Alit
will this solution work ,if the main project is on pcl ?Burkhard
I created the Style.xml since there was none in my Android project, but this is not working for me. Do I need to add something else? Maybe a special theme? I'm using Xamarin.Forms.Noncompliance
Sadly not working for me. The color in the question is when the item is "tapped", which i believe is different to after the tap when it then goes to "selected". This solution works for me on the selected item (after the tap) but "during" the tap i get the same faded pink as in the image in the question.Baldwin
A
68

It looks like there is actually a cross-platform way to do this that works on both iOS and Android (not sure about Windows). It uses only binding and does not require custom renderers (which seems rare). This is a mash-up of lots of googling, so thanks to anyone who I may have borrowed from...

I am assuming ViewCells, but this should work for Text or Image cells as well. I am only including the relevant code here beyond the typical text, image, etc.

On your page do something like this:

MyModel model1 = new MyModel();
MyModel model2 = new MyModel();

ListView list = new ListView
{
    ItemsSource = new List<MyModel> { model1, model2 };
    ItemTemplate = new DataTemplate( typeof(MyCell) )
};

Your custom Model might look something like this:

public class MyModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private Color _backgroundColor;

    public Color BackgroundColor 
    { 
        get { return _backgroundColor; } 
        set 
        { 
            _backgroundColor = value; 

            if ( PropertyChanged != null )
            {
                PropertyChanged( this, new PropertyChangedEventArgs( "BackgroundColor" ) );
            }
        }
    }

    public void SetColors( bool isSelected )
    {
        if ( isSelected )
        {
            BackgroundColor = Color.FromRgb( 0.20, 0.20, 1.0 );
        }
        else
        {
            BackgroundColor = Color.FromRgb( 0.95, 0.95, 0.95 ); 
        }
    }
}

Then for your ItemTemplate you need a custom cell class something like this:

public class MyCell : ViewCell
{
    public MyCell() : base()
    {
        RelativeLayout layout = new RelativeLayout();
        layout.SetBinding( Layout.BackgroundColorProperty, new Binding( "BackgroundColor" ) );

        View = layout;
    }
}

Then in your ItemSelected event handler, do the following. Note that 'selected' is an instance of MyModel used to track the currently selected item. I am only showing background color here, but I also use this technique to reverse highlight the text and detail text colors.

private void ItemSelected( object sender, ItemTappedEventArgs args )
{
    // Deselect previous
    if ( selected != null )
    {
        selected.SetColors( false );
    }

    // Select new
    selected = (list.SelectedItem as MyModel);
    selected.SetColors( true );
}
Ashti answered 12/11, 2014 at 21:15 Comment(11)
This works - at least a little bit. I'm finding that when an item is selected, this works, but when I then select another, the first goes back to the 'old' background - unless I scroll to hide it, then scroll again to reveal it, in which case the 'new' colour is used in the redraw.Microstructure
omg, thank you! I thought of that myself, but when I tried it didn't work. I'm not sure what I did wrong, but copying your code worked.Krishnakrishnah
it is NOT working, you just draw hover the row, old color is still there, and you can still see 1 pixel row of that color at the bottomAftergrowth
@Greag.Deay - ListView.SeparatorVisibility = SeparatorVisibility.None; ListView.SeparatorColor= Color.Transparent; should solve the problem you mentioned.Cr
This method did work fine on my UWP project along with Android and iOS. Kick ass!Agamete
If you are following MVVM pattern this is actually not a best approach. Color is not really related with the model itself - it's a strictly UI matter and framework dependent (Forms in this case). Setting i.e. IsSelected flag in model with converter or style trigger in XAML would be more portable solution.Diplomatist
does not work 100% because at the first selection i still see the default blue for half a second, but it is good enough.Pentobarbital
That's a great solution. I'm not criticizing your solution. But that is too much code to add to change the color of a list item. If you have 10 little fixes like this your code just becomes a mess.Psychochemical
Also this approach will not be able to address the color change for long press, in the scenarios where you have Context Actions defined on the ListView's ViewCellHurds
I would definitetly not handle the BackgroundColor in the viewmodel (MyModel in your case) as this is against the idea of MVVM. The viewmodel is here to prepare data which is then visualized by the view. If we start to mix these concepts,we will be soon back to Winforms times... happy coding then.Shaftesbury
The clean way (to keep view and model separate) is to add bool IsSelected property to item VM. Then in view, use an IValueConverter in ItemTemplate to map isSelected to desired color.Treytri
W
36

iOS

Solution:

Within a custom ViewCellRenderer you can set the SelectedBackgroundView. Simply create a new UIView with a background color of your choice and you're set.

public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
    var cell =  base.GetCell(item, reusableCell, tv);

    cell.SelectedBackgroundView = new UIView {
        BackgroundColor = UIColor.DarkGray,
    };

    return cell;
}

Result:

Note:

With Xamarin.Forms it seems to be important to create a new UIView rather than just setting the background color of the current one.


Android

Solution:

The solution I found on Android is a bit more complicated:

  1. Create a new drawable ViewCellBackground.xml within the Resources>drawable folder:

    <?xml version="1.0" encoding="UTF-8" ?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_pressed="true" >
            <shape android:shape="rectangle">
                <solid android:color="#333333" />
            </shape>
        </item>
        <item>
            <shape android:shape="rectangle">
                <solid android:color="#000000" />
            </shape>
        </item>
    </selector>
    

    It defines solid shapes with different colors for the default state and the "pressed" state of a UI element.

  2. Use a inherited class for the View of your ViewCell, e.g.:

    public class TouchableStackLayout: StackLayout
    {
    }
    
  3. Implement a custom renderer for this class setting the background resource:

    public class ElementRenderer: VisualElementRenderer<Xamarin.Forms.View>
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
        {
            SetBackgroundResource(Resource.Drawable.ViewCellBackground);
    
            base.OnElementChanged(e);
        }
    }
    

Result:

Waybill answered 17/9, 2014 at 9:58 Comment(5)
If setting the background color doesn't work for the existing UIView try calling the cell's SetNeedsLayout (or whatever that method was called) after setting its background color. UITableView tries to not perform layout whenever that's possibleBloodroot
@StenPetrov: Nope, SetNeedsDisplay or ``` SetNeedsLayout``` does not work. But it doesn't really matter, since assigning new UIView {...} is a pretty short workaround.Waybill
And how to change the font color?Bayle
On Android the selected color isn't retained for me. I've tried using android:state_selected.. :[Clerc
SelectedBackgroundView is for selection, not highlightning. Question is about highlightningPierre
B
33

To change color of selected ViewCell, there is a simple process without using custom renderer. Make Tapped event of your ViewCell as below:

<ListView.ItemTemplate>
    <DataTemplate>
        <ViewCell Tapped="ViewCell_Tapped">
            <Label Text="{Binding StudentName}" TextColor="Black" />
        </ViewCell>
    </DataTemplate>
</ListView.ItemTemplate>

In your ContentPage or .cs file, implement the event

private void ViewCell_Tapped(object sender, System.EventArgs e)
{
    if(lastCell != null)
    lastCell.View.BackgroundColor = Color.Transparent;
    var viewCell = (ViewCell)sender;
    if (viewCell.View != null)
    {
        viewCell.View.BackgroundColor = Color.Red;
        lastCell = viewCell;
    }
}

Declare lastCell at the top of your ContentPage like this ViewCell lastCell;

Biagio answered 31/10, 2018 at 9:9 Comment(4)
Best solution! Thanks!Herzl
This solution should have maximum votes and must be on top, as this is the easiest one.Insurgency
The best solution, easiest and most beautiful. Should be on the topUndertaking
It works only when you select an item from the menu. It doesn' work the first time the menu is shown.Elayne
J
19

Only for Android.

Add in your custom theme or your default theme under ProjectName.Android/Resources/values/styles.xml

<item name="android:colorActivatedHighlight">@android:color/transparent</item>
Josephina answered 5/7, 2019 at 11:56 Comment(0)
E
14

I have a similar process, completely cross platform, however I track the selection status myself and I have done this in XAML.

<ListView x:Name="ListView" ItemsSource="{Binding ListSource}" RowHeight="50">
  <ListView.ItemTemplate>
    <DataTemplate>
      <ViewCell>
        <ViewCell.View>
          <ContentView Padding="10" BackgroundColor="{Binding BackgroundColor}">
            <Label Text="{Binding Name}" HorizontalOptions="Center" TextColor="White" />
          </ContentView>
        </ViewCell.View>
      </ViewCell>
    </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

Then in the ItemTapped Event

ListView.ItemTapped += async (s, e) =>
{
    var list = ListSource;
    var listItem = list.First(c => c.Id == ((ListItem)e.Item).Id);
    listItem.Selected = !listItem.Selected;
    SelectListSource = list;
    ListView.SelectedItem = null;
};

As you can see I just set the ListView.SelectedItem to null to remove any of the platform specific selection styles that come into play.

In my model I have

private Boolean _selected;

public Boolean Selected
{
    get => _selected;
    set
    {
        _selected = value;
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs("BackgroundColor"));
    }
}

public Color BackgroundColor
{
    get => Selected ? Color.Black : Color.Blue;
}
Edmonds answered 19/3, 2015 at 4:49 Comment(6)
Will this also for for contextual action menus? Such as long click on android and swipe left on iOS?Algorism
I am unable to call list.First(...) .. It says IEnumerable does not contain a definition for First...Lepidopteran
@Lepidopteran - you will need to add using System.Linq; at the top.Edmonds
If you have using System.Linq, then an IEnumerable certainly has the extension .First(). If you can't resolve this, ask another question on StackOverflow to debug that particular issue.Edmonds
Since the selected item gets changed it triggers ItemSelected event again, so in my case the further code ran into exception.Agnew
The clean way (to keep view and model separate) is to add bool IsSelected property to item VM. Then in view, use an IValueConverter in ItemTemplate to map isSelected to desired color.Treytri
E
13

I had this same issue and I solved it as well by creating a custom renderer for iOS as Falko suggests, however, I avoided the styles modification for Android, I figured out a way to use a custom renderer for Android as well.

It is kind of funky how the selected flag is always false for the android view cell that's why I had to create a new private property to track it. but other than that I think this follows a more appropriate pattern if you want to use custom renderers for both platforms, In my case I did it for TextCell but I believe it applies the same way for other CellViews.

Xamarin Forms

using Xamarin.Forms;

public class CustomTextCell : TextCell
    {
        /// <summary>
        /// The SelectedBackgroundColor property.
        /// </summary>
        public static readonly BindableProperty SelectedBackgroundColorProperty =
            BindableProperty.Create("SelectedBackgroundColor", typeof(Color), typeof(CustomTextCell), Color.Default);

        /// <summary>
        /// Gets or sets the SelectedBackgroundColor.
        /// </summary>
        public Color SelectedBackgroundColor
        {
            get { return (Color)GetValue(SelectedBackgroundColorProperty); }
            set { SetValue(SelectedBackgroundColorProperty, value); }
        }
    }

iOS

public class CustomTextCellRenderer : TextCellRenderer
    {
        public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
        {
            var cell = base.GetCell(item, reusableCell, tv);
            var view = item as CustomTextCell;
            cell.SelectedBackgroundView = new UIView
            {
                BackgroundColor = view.SelectedBackgroundColor.ToUIColor(),
            };

            return cell;
        }
    }

Android

public class CustomTextCellRenderer : TextCellRenderer
{
    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);

        // Save original background to rollback to it when not selected,
        // We assume that no cells will be selected on creation.
        selected = false;
        unselectedBackground = cellCore.Background;

        return cellCore;
    }

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

        if (args.PropertyName == "IsSelected")
        {
            // I had to create a property to track the selection because cellCore.Selected is always false.
            // Toggle selection
            selected = !selected;

            if (selected)
            {
                var customTextCell = sender as CustomTextCell;
                cellCore.SetBackgroundColor(customTextCell.SelectedBackgroundColor.ToAndroid());
            }
            else
            {
                cellCore.SetBackground(unselectedBackground);
            }
        }
    }
}

...then, in the .xaml page, you need to add an XMLNS reference back to the new CustomViewCell...

xmlns:customuicontrols="clr-namespace:MyMobileApp.CustomUIControls"

And don't forget to make actual use of the new Custom comtrol in your XAML.

Escalante answered 20/1, 2017 at 1:40 Comment(8)
Thanks a bunch, this saved me hours! I can confirm it works for ViewCells (which is what I needed)Yawata
Did anyone get this to work while setting a default value for the ListView's SelectedItem?Footton
this works great for ListViews using the RetainElement caching strategy, but for those using the RecycleElement or RecycleElementAndDataTemplate strategy, the IsSelected property never changes - But we do get 'BindingContext' and 'Index' property changes. I'm still trying to figure out if I can use those somehow instead. So close! :)Yawata
Anyone has an idea as to why the SelectedBackgroundColor is never set, when using it in an Implicit style?Handcar
How do you tell CustomTextCell to use CustomTextCellRenderer? Is there some attribute I'm missing?Sing
@Extragorey, you're probably missing the ExportRenderer call: [assembly: ExportRenderer(typeof(ExtendedViewCell), typeof(ExtendedViewCellRenderer))]Shaftesbury
I was using this technique but i have to say that like @FilipeSilva said, it doesn't work if the selected item is initialized in view model. Only work with user interaction. I've tried working around but didn't find a fix.Jockstrap
look here for more informationCayman
H
7

Here is the purely cross platform and neat way:

  1. Define a trigger action:
namespace CustomTriggers {
    public class DeselectListViewItemAction:TriggerAction<ListView> {
        protected override void Invoke(ListView sender) {
            sender.SelectedItem = null;
        }
    }
}
  1. Apply the above class instance as an EventTrigger action in XAML as below:
<ListView x:Name="YourListView" ItemsSource="{Binding ViewModelItems}">
    <ListView.Triggers>
        <EventTrigger Event="ItemSelected">
            <customTriggers:DeselectListViewItemAction></customTriggers:DeselectListViewItemAction>
        </EventTrigger>
    </ListView.Triggers>
</ListView>

Don't forget to add xmlns:customTriggers="clr-namespace:CustomTriggers;assembly=ProjectAssembly"

Note: Because none of your items are in selected mode, selection styling will not get applied on either of the platforms.

Hurds answered 3/3, 2017 at 6:43 Comment(2)
How does this help "set the highlight/background color of a tapped item"? Looks like what you've done is suppress any color change.Treytri
It does not change the background color by any means. But helpful when user wants to suppress the item selection. This is usually helpful in the scenarios when user wants to launch the detail view when an item in the ListView is tapped but wants to suppress the selected item.Hurds
C
2

I have & use a solution similar to @adam-pedley. No custom renderers, in xaml i bind background ViewCell Property

                <ListView x:Name="placesListView" Grid.Row="2" Grid.ColumnSpan="3" ItemsSource="{Binding PlacesCollection}" SelectedItem="{Binding PlaceItemSelected}">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <Grid BackgroundColor="{Binding IsSelected,Converter={StaticResource boolToColor}}">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="auto"/>
                                    <RowDefinition Height="auto"/>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*" />
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>

                                <Label Grid.Row="1" Grid.ColumnSpan="2" Text="{Binding DisplayName}" Style="{StaticResource blubeLabelBlackItalic}" FontSize="Default" HorizontalOptions="Start" />
                                <Label Grid.Row="2" Grid.ColumnSpan="2" Text="{Binding DisplayDetail}"  Style="{StaticResource blubeLabelGrayItalic}" FontSize="Small" HorizontalOptions="Start"/>
                                <!--
                                <Label Grid.RowSpan="2" Grid.ColumnSpan="2" Text="{Binding KmDistance}"  Style="{StaticResource blubeLabelGrayItalic}" FontSize="Default" HorizontalOptions="End" VerticalOptions="Center"/>
                                -->
                            </Grid>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>                    
            </ListView>

In code (MVVM) i save the lastitemselected by a boolToColor Converter i update background color

    public class BoolToColorConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (bool)value ? Color.Yellow : Color.White;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (Color)value == Color.Yellow ? true : false;
        }
    }

    PlaceItem LastItemSelected;

    PlaceItem placeItemSelected;
    public PlaceItem PlaceItemSelected
    {
        get
        {
            return placeItemSelected;
        }

        set
        {
            if (LastItemSelected != null)
                LastItemSelected.IsSelected = false;

            placeItemSelected = value;
            if (placeItemSelected != null)
            {
                placeItemSelected.IsSelected = true;
                LastItemSelected = placeItemSelected;
            }
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PlaceItemSelected)));
        }
    }

My example is extracted by a listview of places which are in a Xamarin Forms Maps (same contentpage). I hope this solution will be usefull for somebody

Curson answered 27/3, 2018 at 13:46 Comment(0)
M
1

In order to set the color of highlighted item you need to set the color of cell.SelectionStyle in iOS.

This example is to set the color of tapped item to transparent.

If you want you can change it with other colors from UITableViewCellSelectionStyle. This is to be written in the platform project of iOS by creating a new Custom ListView renderer in your Forms project.

public class CustomListViewRenderer : ListViewRenderer
    {
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            if (Control == null)
            {
                return;
            }

            if (e.PropertyName == "ItemsSource")
            {
                foreach (var cell in Control.VisibleCells)
                {
                    cell.SelectionStyle = UITableViewCellSelectionStyle.None;
                }
            }
        }
    }

For android you can add this style in your values/styles.xml

<style name="ListViewStyle.Light" parent="android:style/Widget.ListView">
    <item name="android:listSelector">@android:color/transparent</item>
    <item name="android:cacheColorHint">@android:color/transparent</item>
  </style>
Misogynist answered 10/3, 2016 at 10:47 Comment(4)
How does that answer the question? You're not setting a color at all. Although the original highlight color on iOS is too bright, I didn't want to hide the highlighting completely.Waybill
@Waybill - Its setting the colour example, In the example shown I have set to transparent but you can set any colour of your choice.Misogynist
I had to use this iOS custom renderer in addition to Barry Sohl's solution to prevent my templated listview from changing the background color of all controls in the template to the binded background color. However, I did have to change e.PropertyName == "ItemsSource" to e.PropertyName == "SelectedItem" to get it to work properly.Agamete
As Falko mentioned, this doesn't give enough control over color on iOS. UITableViewCellSelectionStyle only mentions two built-in colors, blue and gray.Treytri
N
1

This solution works fine, but if you change the caching strategy of the ListView away from the default value it stops working. It works if you new up your ListView like this: listView = new ListView() { ... }; But if you do this it does not work (the background stays grey for the selected item): listView = new ListView(cachingStrategy:ListViewCachingStrategy.RecycleElement) { ... };

Below is a solution that works even with a non-standard cachingStrategy. I prefer this to other solutions like having code in the OnItemSelected method coupled with a binding from the ViewModel for the background color.

Credit to @Lang_tu_bi_dien who posted the idea here: Listview Selected Item Background Color

The final code then looks like this:

Xamarin.Forms code:

namespace MyProject
{
    public class ListView2 : ListView
    {
        public ListView2(ListViewCachingStrategy cachingStrategy) : base(cachingStrategy)
        {
        }
    }
}

XAML on your page:

    <ListView2 x:Name="myListView" ListViewCachingStrategy="RecycleElement" ItemsSource="{Binding ListSource}" RowHeight="50">
        <ListView.ItemTemplate>
          <DataTemplate>
            <ViewCell>
              <ViewCell.View>
                  <Label Text="{Binding Name}" HorizontalOptions="Center" TextColor="White" />
                </ContentView>
              </ViewCell.View>
            </ViewCell>
          </DataTemplate>
        </ListView.ItemTemplate>
    </ListView2>

iOS-specific renderer:

[assembly: ExportRenderer(typeof(ListView2), typeof(ListView2Renderer))]
namespace MyProject.iOS
{
    public partial class ListView2Renderer : ListViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
        {
            base.OnElementChanged(e);
            if (Control != null && e != null)
            {
                //oldDelegate = (UITableViewSource)Control.Delegate;
                Control.Delegate = new ListView2Delegate(e.NewElement);
            }
        }
    }


    class ListView2Delegate : UITableViewDelegate
    {
        private ListView _listView;

        internal ListView2Delegate(ListView listView)
        {
            _listView = listView;
        }

        public override void WillDisplay(UITableView tableView, UITableViewCell cell, Foundation.NSIndexPath indexPath)
        {
            cell.SelectedBackgroundView = new UIView()
            {
                BackgroundColor = Color.Red.ToUIColor()
            };
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _listView = null;
            }
            base.Dispose(disposing);
        }
    }
}

Note: you may run into some issues due to the fact that you are replacing the default delegate, for more info on this see Setting delegate of control in custom renderer results in lost functionality. In my project it all works as it should if I do this:

  • Use the normal ListView together with the ListItemViewCellRenderer code given in in the earlier posts on this thread for ListViews that use the default caching strategy ListViewCachingStrategy.RetainElement.

  • Use this ListView2 together for ListViews that use a non-default caching strategy i.e. ListViewCachingStrategy.RecycleElement or ListViewCachingStrategy.RecycleElementAndDataTemplate.

I am also filing a feature request with Xamarin, please upvote it if you feel this should be added to the standard ListView: ListView desperately needs a SelectedItemBackgroundColor property

Napolitano answered 1/11, 2017 at 12:20 Comment(0)
L
1

Found this lovely option using effects here.

iOS:

[assembly: ResolutionGroupName("MyEffects")]
[assembly: ExportEffect(typeof(ListViewHighlightEffect), nameof(ListViewHighlightEffect))]
namespace Effects.iOS.Effects
{
    public class ListViewHighlightEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            var listView = (UIKit.UITableView)Control;

            listView.AllowsSelection = false;
        }

        protected override void OnDetached()
        {
        }
    }
}

Android:

[assembly: ResolutionGroupName("MyEffects")]
[assembly: ExportEffect(typeof(ListViewHighlightEffect), nameof(ListViewHighlightEffect))]
namespace Effects.Droid.Effects
{
    public class ListViewHighlightEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            var listView = (Android.Widget.ListView)Control;

            listView.ChoiceMode = ChoiceMode.None;
        }

        protected override void OnDetached()
        {
        }
    }
}

Forms:

ListView_Demo.Effects.Add(Effect.Resolve($"MyEffects.ListViewHighlightEffect"));
Latent answered 7/2, 2018 at 17:38 Comment(4)
I think this does not answer the question, since you're not setting the color of the tapped item. You're just avoiding the selection altogether.Waybill
Not only does this not answer the question, this makes it impossible to select a row!Treytri
Fair point, must have misread the question as I found my way here looking for a way to remove the selected row colour. I do still manage to select a row, through tap gestures if I remember correctly.Latent
Tried it: Can no longer select a row when this effect is active. Rollback changes...Shaftesbury
G
1

The easiest way to accomplish this on android is by adding the following code to your custom style :

@android:color/transparent

Gaylagayle answered 12/5, 2018 at 23:48 Comment(0)
A
0

The previous answers either suggest custom renderers or require you to keep track of the selected item either in your data objects or otherwise. This isn't really required, there is a way to link to the functioning of the ListView in a platform agnostic way. This can then be used to change the selected item in any way required. Colors can be modified, different parts of the cell shown or hidden depending on the selected state.

Let's add an IsSelected property to our ViewCell. There is no need to add it to the data object; the listview selects the cell, not the bound data.

public partial class SelectableCell : ViewCell {

  public static readonly BindableProperty IsSelectedProperty = BindableProperty.Create(nameof(IsSelected), typeof(bool), typeof(SelectableCell), false, propertyChanged: OnIsSelectedPropertyChanged);
  public bool IsSelected {
    get => (bool)GetValue(IsSelectedProperty);
    set => SetValue(IsSelectedProperty, value);
  }

  // You can omit this if you only want to use IsSelected via binding in XAML
  private static void OnIsSelectedPropertyChanged(BindableObject bindable, object oldValue, object newValue) {
    var cell = ((SelectableCell)bindable);
    // change color, visibility, whatever depending on (bool)newValue
  }

  // ...
}

To create the missing link between the cells and the selection in the list view, we need a converter (the original idea came from the Xamarin Forum):

public class IsSelectedConverter : IValueConverter {
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
    value != null && value == ((ViewCell)parameter).View.BindingContext;

  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
    throw new NotImplementedException();
}

We connect the two using this converter:

<ListView x:Name="ListViewName">
  <ListView.ItemTemplate>
    <DataTemplate>
      <local:SelectableCell x:Name="ListViewCell"
        IsSelected="{Binding SelectedItem, Source={x:Reference ListViewName}, Converter={StaticResource IsSelectedConverter}, ConverterParameter={x:Reference ListViewCell}}" />
    </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

This relatively complex binding serves to check which actual item is currently selected. It compares the SelectedItem property of the list view to the BindingContext of the view in the cell. That binding context is the data object we actually bind to. In other words, it checks whether the data object pointed to by SelectedItem is actually the data object in the cell. If they are the same, we have the selected cell. We bind this into to the IsSelected property which can then be used in XAML or code behind to see if the view cell is in the selected state.

There is just one caveat: if you want to set a default selected item when your page displays, you need to be a bit clever. Unfortunately, Xamarin Forms has no page Displayed event, we only have Appearing and this is too early for setting the default: the binding won't be executed then. So, use a little delay:

protected override async void OnAppearing() {
  base.OnAppearing();

  Device.BeginInvokeOnMainThread(async () => {
    await Task.Delay(100);
    ListViewName.SelectedItem = ...;
  });
}
Antisemite answered 28/11, 2018 at 20:15 Comment(4)
Never use such code in production software. It may works on your machine, but whenever you use "some delay to wait a bit" this is an indicator for problems. What is 100ms is not enough on a very slow Nokia 3 Android phone?Shaftesbury
Furthermore, having a direct reference between the ListView's Name and the ItemTemplate means that you'll never be able to extract the DataTemplate to a separate file. If the DataTemplate grows large, you gonna end up with huge xaml files (as we did many times).Shaftesbury
I've left Xamarin for Flutter more than a year ago and never looked back. Still, at the time this answer was written, this probably was the only way to accomplish it. If things have changed since and you know a better solution now, then you could edit, or better yet, provide a new answer.Austral
FWIW re @Shaftesbury "Never use such code in production software." I've been using XForms for years, and pushed it to its limit on many fronts. Unfortunately, the lack (still!) of an OnAppeared method requires either custom renderers per platform to implement such a method, or exactly that Delay hack shown in the answer. I agree that the delay hack is awful; in fact it has to be increased for more complex pages, even on fast devices! (Hopefully Maui will eventually get OnAppeared added to it.)Treytri
P
0

The easiest way to change the selection color is adding these to your Android.Resources.values.styles

  <item name="android:colorPressedHighlight">@android:color/holo_blue_bright</item>
  <item name="android:colorFocusedHighlight">@android:color/holo_blue_bright</item>
  <item name="android:colorActivatedHighlight">@android:color/holo_blue_bright</item>
Puttier answered 21/2, 2022 at 9:42 Comment(1)
Please check existing answers carefully, before adding a new one. This was covered years earlier in the highest-voted answer: https://mcmap.net/q/238157/-xamarin-forms-listview-set-the-highlight-color-of-a-tapped-item.Treytri
A
0

For iOS, you can add a new ListViewRender to override the default selectedBackgroundView's backgroundColor value:

public class ListViewRenderer : Xamarin.Forms.Platform.iOS.ListViewRenderer
{
    public ListViewRenderer()
    {
    }

    public override void LayoutSubviews()
    {
        base.LayoutSubviews();

        var controller = this.ViewController as UITableViewController;
        if (controller != null)
        {
            var tableView = controller.TableView;
            if (tableView != null && tableView.Subviews != null)
            {
                //For lower iOS version, this line must add, otherwise it can not find any UITableViewCell
                tableView.LayoutSubviews();

                var backgroundColor = Color.Red.ToUIColor();
                foreach (var subview in tableView.Subviews)
                {
                    if (subview is UITableViewCell tableViewCell)
                    {
                        tableViewCell.SelectedBackgroundView = new UIView
                        {
                            BackgroundColor = backgroundColor
                        };
                        tableViewCell.MultipleSelectionBackgroundView = new UIView
                        {
                            BackgroundColor = backgroundColor
                        };
                    }
                }
            }
        }
    }
}
Assumed answered 18/5, 2023 at 13:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.