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

匿名 (未验证) 提交于 2019-12-03 02:50:02

问题:

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")

回答1:

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 { 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 ); } 

I have screenshots from both iOS and Android if someone wants to bump me to 10 points so that I can actually post them :)



回答2:

In Android simply edit your Style.xml file under Resources\Value adding this:

#96BCE3#E39696


回答3:

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, UITableView tv) {     var cell = base.GetCell(item, 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:

    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 {     protected override void OnElementChanged(ElementChangedEventArgs e)     {         SetBackgroundResource(Resource.Drawable.ViewCellBackground);          base.OnElementChanged(e);     } } 

Result:



回答4:

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

                  

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             {                 return _selected;             }             set             {                 _selected = value;                 if (PropertyChanged != null)                     PropertyChanged(this, new PropertyChangedEventArgs("BackgroundColor"));             }         }                           public Color BackgroundColor         {             get             {                 if (Selected)                     return Color.Black;                 else                     return Color.Blue             }         } 


回答5:

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

public class CustomTextCell : TextCell     {         ///          /// The SelectedBackgroundColor property.         ///          public static readonly BindableProperty SelectedBackgroundColorProperty =             BindableProperty.Create("SelectedBackgroundColor", typeof(Color), typeof(CustomTextCell), Color.Default);          ///          /// Gets or sets the SelectedBackgroundColor.         ///          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);             }         }     } } 


回答6:

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



回答7:

Here is the purely cross platform and neat way:

1) Define a trigger action

namespace CustomTriggers {    public class DeselectListViewItemAction:TriggerAction {        protected override void Invoke(ListView sender) {                 sender.SelectedItem = null;        }    } } 

2) Apply the above class instance as an EventTrigger action in XAML as below

 

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.



回答8:

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")); 


回答9:

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:

                                    

iOS-specific renderer:

[assembly: ExportRenderer(typeof(ListView2), typeof(ListView2Renderer))] namespace MyProject.iOS {     public partial class ListView2Renderer : ListViewRenderer     {         protected override void OnElementChanged(ElementChangedEventArgs 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



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!