android - item - listview xamarin forms example
Xamarin.Forms ListView: establece el color de resaltado de un elemento seleccionado (14)
iOS
Solución:
Dentro de un
ViewCellRenderer
personalizado, puede configurar
SelectedBackgroundView
.
Simplemente cree una nueva
UIView
con un color de fondo de su 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 importante crear una
nueva
UIView
lugar de solo establecer el color de fondo de la actual.
Androide
Solución:
La solución que encontré en Android es un poco más complicada:
-
Cree un nuevo
ViewCellBackground.xml
dentro de la carpetaResources
>drawable
:<?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>
Define formas sólidas con diferentes colores para el estado predeterminado y el estado "presionado" de un elemento de la interfaz de usuario.
-
Use una clase heredada para la
View
de suViewCell
, por ejemplo:public class TouchableStackLayout: StackLayout { }
-
Implemente un renderizador personalizado para esta clase configurando el recurso de fondo:
public class ElementRenderer: VisualElementRenderer<Xamarin.Forms.View> { protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e) { SetBackgroundResource(Resource.Drawable.ViewCellBackground); base.OnElementChanged(e); } }
Resultado:
Usando Xamarin.Forms , ¿cómo puedo definir el color de fondo / resaltado de un elemento ListView seleccionado / tocado?
(Mi lista tiene un fondo negro y un color de texto blanco, por lo que el color de resaltado predeterminado en iOS es demasiado brillante. En contraste, en Android no hay resaltado en absoluto, hasta una sutil línea gris horizontal).
Ejemplo: (izquierda: iOS, derecha: Android; mientras presiona "Barn2")
Aquí está la plataforma puramente cruzada y ordenada:
1) Definir una acción desencadenante
namespace CustomTriggers {
public class DeselectListViewItemAction:TriggerAction<ListView> {
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
<ListView x:Name="YourListView" ItemsSource="{Binding ViewModelItems}">
<ListView.Triggers>
<EventTrigger Event="ItemSelected">
<customTriggers:DeselectListViewItemAction></customTriggers:DeselectListViewItemAction>
</EventTrigger>
</ListView.Triggers>
</ListView>
No olvide agregar
xmlns:customTriggers="clr-namespace:CustomTriggers;assembly=ProjectAssembly"
Nota: Debido a que ninguno de sus elementos está en modo seleccionado, el estilo de selección no se aplicará en ninguna de las plataformas.
En Android simplemente edite su archivo styles.xml en Recursos / valores agregando esto:
<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>
Encontré esta hermosa opción usando efectos 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()
{
}
}
}
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()
{
}
}
}
Formas:
ListView_Demo.Effects.Add(Effect.Resolve($"MyEffects.ListViewHighlightEffect"));
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 almacenamiento en caché no estándar. Prefiero esto a otras soluciones como tener código en el método OnItemSelected junto con un enlace desde ViewModel para el color de fondo.
Crédito a @Lang_tu_bi_dien que publicó la idea aquí: Vista de lista Elemento seleccionado Color de fondo
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:
<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>
Representador específico de iOS:
[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);
}
}
}
Nota: puede encontrarse con algunos problemas debido al hecho de que está reemplazando el delegado predeterminado; para obtener más información al respecto, consulte Establecer el delegado de control en el renderizador personalizado da como resultado la pérdida de la funcionalidad . En mi proyecto, todo funciona como debería si hago esto:
-
Use el ListView normal junto con el código ListItemViewCellRenderer indicado en las publicaciones anteriores de este hilo para ListViews que usan la estrategia de almacenamiento en caché predeterminada ListViewCachingStrategy.RetainElement.
-
Use este ListView2 juntos para ListViews que usan una estrategia de almacenamiento en caché no predeterminada, es decir, ListViewCachingStrategy.RecycleElement o ListViewCachingStrategy.RecycleElementAndDataTemplate.
También estoy presentando una solicitud de función con Xamarin, favor de votarla si cree que debería agregarse a ListView estándar: ListView necesita desesperadamente una propiedad SelectedItemBackgroundColor
La forma más fácil de lograr esto en Android es agregando el siguiente código a su estilo personalizado:
@android: color / transparente
Las respuestas anteriores sugieren procesadores personalizados o requieren que realice un seguimiento del elemento seleccionado en sus objetos de datos o de otra manera.
Esto no es realmente necesario, hay una manera de vincular el funcionamiento de
ListView
de una manera independiente de la plataforma.
Esto se puede usar para cambiar el elemento seleccionado de cualquier forma requerida.
Los colores se pueden modificar, se muestran u ocultan diferentes partes de la celda según el estado seleccionado.
IsSelected
una propiedad
IsSelected
a nuestra
ViewCell
.
No es necesario agregarlo al objeto de datos;
La vista de lista selecciona la celda, no los datos vinculados.
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
}
// ...
}
Para crear el enlace que falta entre las celdas y la selección en la vista de lista, necesitamos un convertidor (la idea original vino del Foro Xamarin ):
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();
}
Conectamos los dos usando este convertidor:
<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>
Este enlace relativamente complejo sirve para verificar qué elemento real está seleccionado actualmente.
Compara la propiedad
SelectedItem
de la vista de lista con el
BindingContext
de la vista en la celda.
Ese contexto vinculante es el objeto de datos al que nos vinculamos.
En otras palabras, verifica si el objeto de datos señalado por
SelectedItem
es realmente el objeto de datos en la celda.
Si son iguales, tenemos la celda seleccionada.
IsSelected
esto a la propiedad
IsSelected
que luego se puede usar en XAML o en el código para ver si la celda de vista está en el estado seleccionado.
Solo hay una advertencia: si desea establecer un elemento seleccionado predeterminado cuando se muestre su página, debe ser un poco inteligente. Desafortunadamente, Xamarin Forms no tiene ningún evento de Página mostrada, solo tenemos Aparición y esto es demasiado pronto para establecer el valor predeterminado: el enlace no se ejecutará en ese momento. Entonces, usa un poco de retraso:
protected override async void OnAppearing() {
base.OnAppearing();
Device.BeginInvokeOnMainThread(async () => {
await Task.Delay(100);
ListViewName.SelectedItem = ...;
});
}
Para cambiar el color de
ViewCell
seleccionado, hay un proceso simple sin usar un renderizador personalizado.
Haga un evento
ViewCell
de su
ViewCell
como se muestra a continuación
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell Tapped="ViewCell_Tapped">
<Label Text="{Binding StudentName}" TextColor="Black" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
En su ContentPage o archivo .cs, implemente el evento
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
en la parte superior de su
ContentPage
como esta
ViewCell lastCell;
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 tocado en transparente.
Si lo desea, puede cambiarlo con otros colores de
UITableViewCellSelectionStyle
.
Esto se escribirá en el proyecto de plataforma de iOS creando un nuevo renderizador Custom ListView en su proyecto de Formularios.
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, puede agregar este estilo en su 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>
Parece que en realidad hay una forma multiplataforma para hacer esto que funciona tanto en iOS como en Android (no estoy seguro acerca de Windows). Utiliza solo enlaces y no requiere renderizadores personalizados (lo que parece raro). Esta es una combinación de muchas búsquedas en Google, así que gracias a cualquiera a quien me haya prestado ...
Asumo ViewCells, pero esto también debería funcionar para las celdas de texto o imagen. Solo incluyo el código relevante aquí más allá del texto típico, imagen, etc.
En su página, haga algo como esto:
MyModel model1 = new MyModel();
MyModel model2 = new MyModel();
ListView list = new ListView
{
ItemsSource = new List<MyModel> { model1, model2 };
ItemTemplate = new DataTemplate( typeof(MyCell) )
};
Su modelo personalizado podría verse 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 similar a esta:
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 eventos ItemSelected, haga lo siguiente. Tenga en cuenta que ''seleccionado'' es una instancia de MyModel utilizada para rastrear el elemento actualmente seleccionado. Aquí solo muestro el color de fondo, pero también utilizo esta técnica para 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 );
}
Solo para Android
Agregue su tema personalizado
<item name="android:colorActivatedHighlight">@android:color/transparent</item>
Tengo un proceso similar, completamente multiplataforma, sin embargo, realizo un seguimiento del estado de selección y lo hice en 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>
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, acabo de establecer ListView.SelectedItem en 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
}
}
Tengo y uso una solución similar a @ adam-pedley. Sin renderizadores personalizados, en 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>
En el código (MVVM) guardo el último elemento seleccionado por un convertidor boolToColor y 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 es extraído por 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
Tuve este mismo problema y también lo resolví creando un renderizador personalizado para iOS como sugiere Falko, sin embargo, evité la modificación de estilos para Android, descubrí una forma de usar un renderizador personalizado para Android también.
Es un poco extraño cómo la bandera seleccionada siempre es falsa para la celda de vista de Android, por eso tuve que crear una nueva propiedad privada para rastrearla. pero aparte de eso, creo que esto sigue un patrón más apropiado si desea utilizar renderizadores personalizados para ambas plataformas. En mi caso, lo hice para TextCell pero creo que se aplica de la misma manera para otros CellViews.
Formas Xamarin
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;
}
}
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);
}
}
}
}
... luego, en la página .xaml, debe agregar una referencia XMLNS al nuevo CustomViewCell ...
xmlns:customuicontrols="clr-namespace:MyMobileApp.CustomUIControls"
Y no olvide hacer uso real del nuevo control personalizado en su XAML.