Xamarin.Forms ListView: establece el color de resaltado de un elemento tocado

Usando Xamarin.Forms , ¿cómo puedo definir el color de resaltado / fondo de un elemento ListView seleccionado / girado?

(Mi lista tiene un fondo negro y un color de texto blanco, por lo que el color de resaltado predeterminado en iOS es demasiado shiny. Por el contrario, en Android no hay ningún resalte en absoluto, hasta una línea gris horizontal sutil).

Ejemplo: (izquierda: iOS, derecha: Android, mientras presiona “Barn2”)

Parece que en realidad hay una forma de plataforma cruzada para hacer esto que funciona tanto en iOS como en Android (no estoy seguro acerca de Windows). Solo utiliza enlaces y no requiere procesadores personalizados (lo que parece raro). Este es un montón de Google, así que gracias a todos los que pueda haber pedido prestado …

Estoy asumiendo ViewCells, pero esto también debería funcionar para las celdas de Texto o Imagen. Solo estoy incluyendo el código relevante aquí más allá del texto, imagen típica, etc.

En tu página haz algo como esto:

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

Su modelo personalizado podría verse más o menos así:

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

Luego, para su ItemTemplate necesita una clase de celda personalizada algo como esto:

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

Luego, en su controlador de evento ItemSelected, haga lo siguiente. Tenga en cuenta que ‘seleccionado’ es una instancia de MyModel utilizada para rastrear el elemento seleccionado actualmente. Solo estoy mostrando el color de fondo aquí, pero también uso esta técnica para revertir resaltar el texto y detallar los colores del texto.

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

Tengo capturas de pantalla de iOS y Android si alguien quiere ponerme 10 puntos para que pueda publicarlos 🙂

En Android, simplemente edite su archivo Style.xml en Resources \ Value agregando esto:

   #96BCE3 #E39696  

iOS

Solución:

Dentro de un ViewCellRenderer personalizado puede establecer SelectedBackgroundView . Simplemente crea una nueva UIView con un color de fondo de tu elección y listo.

 public override UITableViewCell GetCell(Cell item, UITableView tv) { var cell = base.GetCell(item, tv); cell.SelectedBackgroundView = new UIView { BackgroundColor = UIColor.DarkGray, }; return cell; } 

Resultado:

Nota:

Con Xamarin.Forms parece ser importante crear una nueva UIView lugar de simplemente establecer el color de fondo de la actual.


Androide

Solución:

La solución que encontré en Android es un poco más complicada:

  1. Cree un nuevo ViewCellBackground.xml en la carpeta Resources > drawable :

                  

    Define formas sólidas con diferentes colores para el estado predeterminado y el estado “presionado” de un elemento UI.

  2. Use una clase heredada para la View de su ViewCell , por ejemplo:

     public class TouchableStackLayout: StackLayout { } 
  3. Implemente un renderizador personalizado para esta clase configurando el recurso de fondo:

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

Resultado:

Tengo un proceso similar, completamente multiplataforma; sin embargo, sigo el estado de la selección y lo hice en XAML.

               

Luego, en el evento ItemTapped

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

Como puede ver, simplemente configuré ListView.SelectedItem como nulo para eliminar cualquiera de los estilos de selección específicos de la plataforma que entran en juego.

En mi modelo tengo

  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 } } 

Tuve el mismo problema y lo resolví también al crear un renderizador personalizado para iOS, como sugiere Falko. Sin embargo, evité la modificación de estilos para Android. También encontré la manera de usar un renderizador personalizado para Android.

Es curioso cómo la bandera seleccionada es siempre falsa para la celda de vista de Android, es por eso que tuve que crear una nueva propiedad privada para rastrearla. pero aparte de eso, creo que esto sigue un patrón más apropiado si quieres usar procesadores personalizados para ambas plataformas, en mi caso lo hice para TextCell, pero creo que se aplica de la misma manera para otras CellViews.

Formularios Xamarin

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

Androide

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

Aquí está la manera puramente cruzada y ordenada:

1) Definir una acción desencadenante

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

2) Aplicar la instancia de clase anterior como una acción EventTrigger en XAML como se muestra a continuación

         

No olvides agregar xmlns:customTriggers="clr-namespace:CustomTriggers;assembly=ProjectAssembly"

Nota: Como ninguno de sus elementos está en el modo seleccionado, el estilo de selección no se aplicará en ninguna de las plataformas.

Para establecer el color del elemento resaltado, debe establecer el color de cell.SelectionStyle en iOS.

Este ejemplo es para establecer el color del elemento golpeado a transparente.

Si lo deseas, puedes cambiarlo con otros colores de UITableViewCellSelectionStyle . Esto debe escribirse en el proyecto de plataforma de iOS creando un nuevo renderizador personalizado ListView en su proyecto Forms.

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

Para Android, puedes agregar este estilo en tus valores / styles.xml

  

Esta solución funciona bien, pero si cambia la estrategia de almacenamiento en caché de ListView lejos del valor predeterminado, deja de funcionar. Funciona si actualiza su ListView de esta manera: listView = new ListView() { ... }; Pero si hace esto, no funciona (el fondo permanece gris para el elemento seleccionado): listView = new ListView(cachingStrategy:ListViewCachingStrategy.RecycleElement) { ... };

A continuación se muestra una solución que funciona incluso con una estrategia de caché no estándar. Prefiero esto a otras soluciones como tener código en el método OnItemSelected junto con un enlace del ViewModel para el color de fondo.

Crédito a @Lang_tu_bi_dien que publicó la idea aquí: Listview Selected Item Background Color

El código final se ve así:

Código Xamarin.Forms:

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

XAML en tu página:

              

Encontré esta hermosa opción usando efectos aquí .

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

Androide:

 [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() { } } } 

Formularios:

 ListView_Demo.Effects.Add(Effect.Resolve($"MyEffects.ListViewHighlightEffect")); 

La forma más fácil de lograr esto en Android es agregar el siguiente código a su estilo personalizado:

@android: color / transparente

Tengo y uso una solución similar a @ adam-pedley. No hay representadores personalizados, en xaml me unen fondo ViewCell Property

                       

En el código (MVVM) guardo el último elemento seleccionado por un convertidor boolToColor actualizo el color de fondo

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

Mi ejemplo se extrae mediante una vista de lista de lugares que están en un Xamarin Forms Maps (misma página de contenido). Espero que esta solución sea útil para alguien