使用Xamarin.Forms,如何定义选定/点击的ListView项目的突出显示/背景颜色?
(我的列表具有黑色背景和白色文本颜色,因此iOS上的默认突出显示颜色太亮。相比之下,Android上根本没有突出显示-直到细微的水平灰线为止。)
示例:(左:iOS,右:Android;同时按下“ Barn2”)
Answers:
在Android中,只需在Resources \ values下编辑您的styles.xml文件,然后添加以下代码:
<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>
看来实际上有一种跨平台的方法可以同时在iOS和Android(对于Windows不确定)上运行。它仅使用绑定,不需要自定义渲染器(这似乎很少)。这是很多谷歌搜索的集合,所以要感谢我可能从中借来的任何人...
我假设使用ViewCells,但这也适用于Text或Image单元。我仅在此处包括典型文本,图像等之外的相关代码。
在您的页面上执行以下操作:
MyModel model1 = new MyModel();
MyModel model2 = new MyModel();
ListView list = new ListView
{
ItemsSource = new List<MyModel> { model1, model2 };
ItemTemplate = new DataTemplate( typeof(MyCell) )
};
您的自定义模型可能如下所示:
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 );
}
}
}
然后,对于您的ItemTemplate,您需要一个类似以下内容的自定义单元格类:
public class MyCell : ViewCell
{
public MyCell() : base()
{
RelativeLayout layout = new RelativeLayout();
layout.SetBinding( Layout.BackgroundColorProperty, new Binding( "BackgroundColor" ) );
View = layout;
}
}
然后在您的ItemSelected事件处理程序中,执行以下操作。请注意,“ selected”是MyModel的一个实例,用于跟踪当前选定的项目。我仅在此处显示背景色,但我也使用此技术来反向突出显示文本和细节文本颜色。
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 );
}
ListView.SeparatorVisibility = SeparatorVisibility.None; ListView.SeparatorColor= Color.Transparent;
应该解决您提到的问题。
解:
在自定义中,ViewCellRenderer
您可以设置SelectedBackgroundView
。只需创建一个UIView
具有您选择的背景色的新产品即可完成设置。
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;
}
结果:
注意:
使用Xamarin.Forms似乎很重要,这是创建一个新的 UIView
而不是仅仅设置当前颜色的背景色。
解:
我在Android上找到的解决方案要复杂一些:
ViewCellBackground.xml
在Resources
>drawable
文件夹中创建一个新的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>
它为UI元素的默认状态和“按下”状态定义了具有不同颜色的实体形状。
View
为您的ViewCell
,使用继承的类,例如:
public class TouchableStackLayout: StackLayout
{
}
为此类设置背景资源,实现一个自定义渲染器:
public class ElementRenderer: VisualElementRenderer<Xamarin.Forms.View>
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
{
SetBackgroundResource(Resource.Drawable.ViewCellBackground);
base.OnElementChanged(e);
}
}
结果:
SetNeedsDisplay
或者```SetNeedsLayout```不起作用。但这并不重要,因为分配new UIView {...}
是一个很短的解决方法。
要更改selected的颜色ViewCell
,有一个简单的过程无需使用自定义渲染器。使Tapped
您的事件ViewCell
如下
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell Tapped="ViewCell_Tapped">
<Label Text="{Binding StudentName}" TextColor="Black" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
在您的ContentPage或.cs文件中,实现事件
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;
}
}
像这样lastCell
在您的顶部声明ContentPage
ViewCell lastCell;
仅适用于Android
添加您的自定义主题
<item name="android:colorActivatedHighlight">@android:color/transparent</item>
我有一个类似的过程,完全跨平台,但是我自己跟踪选择状态,并且我已经在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>
然后在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;
};
如您所见,我只是将ListView.SelectedItem设置为null来删除任何正在发挥作用的特定于平台的选择样式。
在我的模型中
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;
}
ItemSelected
,因此它再次触发事件,因此在我的情况下,其他代码遇到了异常。
我遇到了同样的问题,我也按照Falko的建议通过为iOS创建自定义渲染器来解决了这个问题,但是,我避免了对Android的样式修改,因此我想出了一种也可以为Android使用自定义渲染器的方法。
对于android view cell来说,所选标志始终为false有点奇怪,这就是为什么我必须创建一个新的private属性来对其进行跟踪的原因。但是除此之外,如果您想在两个平台上都使用自定义渲染器,那么我认为这遵循更合适的模式。就我而言,我对TextCell做到了这一点,但我相信它对其他CellView也会采用相同的方式。
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;
}
}
安卓
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);
}
}
}
}
...然后,在.xaml页面中,您需要将XMLNS引用添加回新的CustomViewCell ...
xmlns:customuicontrols="clr-namespace:MyMobileApp.CustomUIControls"
并且不要忘记在XAML中实际使用新的Custom comtrol。
这是纯粹的跨平台和整洁的方式:
1)定义触发动作
namespace CustomTriggers {
public class DeselectListViewItemAction:TriggerAction<ListView> {
protected override void Invoke(ListView sender) {
sender.SelectedItem = null;
}
}
}
2)将上述类实例作为XAML中的EventTrigger操作应用,如下所示
<ListView x:Name="YourListView" ItemsSource="{Binding ViewModelItems}">
<ListView.Triggers>
<EventTrigger Event="ItemSelected">
<customTriggers:DeselectListViewItemAction></customTriggers:DeselectListViewItemAction>
</EventTrigger>
</ListView.Triggers>
</ListView>
不要忘记添加 xmlns:customTriggers="clr-namespace:CustomTriggers;assembly=ProjectAssembly"
注意:由于所有项目都不处于选定模式,因此选择样式将不会应用到两个平台上。
ListView
点击的某个项目但想隐藏所选项目时启动详细视图时。
我有&使用类似于@ adam-pedley的解决方案。没有自定义渲染器,在XAML中我绑定了背景ViewCell属性
<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>
在代码(MVVM)中,我保存由boolToColor Converter选择的lastitem我更新背景色
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)));
}
}
我的示例是通过Xamarin表单地图(相同的内容页面)中的位置的列表视图提取的。我希望这个解决方案对某人有用
为了设置突出显示的项目的颜色,您需要cell.SelectionStyle
在iOS中设置的颜色。
本示例将点击项目的颜色设置为透明。
如果需要,可以用中的其他颜色进行更改UITableViewCellSelectionStyle
。通过在Forms项目中创建一个新的Custom ListView渲染器,可以在iOS的平台项目中编写该代码。
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;
}
}
}
}
对于android,您可以在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>
e.PropertyName == "ItemsSource"
,以e.PropertyName == "SelectedItem"
得到它才能正常工作。
UITableViewCellSelectionStyle
只提到了两种内置颜色,蓝色和灰色。
此解决方案可以正常工作,但是如果您将ListView的缓存策略更改为默认值,它将停止工作。如果像这样新建ListView,它会起作用:
listView = new ListView() { ... };
但是,如果这样做,它将不起作用(所选项目的背景保持灰色):
listView = new ListView(cachingStrategy:ListViewCachingStrategy.RecycleElement) { ... };
以下是即使使用非标准的cachingStrategy也可以使用的解决方案。与其他解决方案相比,我更喜欢此解决方案,例如在OnItemSelected方法中添加代码,再加上ViewModel对背景色的绑定。
感谢@Lang_tu_bi_dien,他在此处发布了该想法:Listview选定项目的背景色
最终的代码如下所示:
Xamarin.Forms代码:
namespace MyProject
{
public class ListView2 : ListView
{
public ListView2(ListViewCachingStrategy cachingStrategy) : base(cachingStrategy)
{
}
}
}
页面上的XAML:
<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专用渲染器:
[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);
}
}
}
注意:由于您要替换默认委托,因此可能会遇到一些问题,有关更多信息,请参见在自定义渲染器中设置控件委托会导致功能丧失。在我的项目中,如果我这样做,一切都会正常进行:
对于使用默认缓存策略ListViewCachingStrategy.RetainElement的ListView,将普通ListView与该线程前面的文章中给出的ListItemViewCellRenderer代码一起使用。
将此ListView2一起用于使用非默认缓存策略(即ListViewCachingStrategy.RecycleElement或ListViewCachingStrategy.RecycleElementAndDataTemplate)的ListView。
我也在向Xamarin提出功能请求,如果您认为应该将其添加到标准ListView中,请对其进行投票:ListView迫切需要SelectedItemBackgroundColor属性
在这里使用效果找到了这个可爱的选项。
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()
{
}
}
}
形式:
ListView_Demo.Effects.Add(Effect.Resolve($"MyEffects.ListViewHighlightEffect"));
在android上最简单的方法是在您的自定义样式中添加以下代码:
@android:颜色/透明
先前的答案要么建议使用自定义渲染器,要么要求您在数据对象中或其他位置跟踪所选项目。确实不是必需的,有一种方法可以以与ListView
平台无关的方式链接到s的功能。然后可以使用它以所需的任何方式更改所选项目。可以修改颜色,根据所选状态显示或隐藏单元格的不同部分。
让我们向中添加一个IsSelected
属性ViewCell
。无需将其添加到数据对象。列表视图选择单元格,而不是绑定的数据。
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
}
// ...
}
为了在列表视图中的单元格和选择之间创建缺失的链接,我们需要一个转换器(最初的想法来自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();
}
我们使用此转换器连接两者:
<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>
这种相对复杂的绑定用于检查当前选择了哪个实际项目。它将SelectedItem
列表视图的属性BindingContext
与单元格中视图的属性进行比较。该绑定上下文是我们实际绑定到的数据对象。换句话说,它检查所指向的数据对象SelectedItem
是否实际上是单元中的数据对象。如果它们相同,则我们具有选定的单元格。我们将此绑定到该IsSelected
属性,然后可以在XAML中使用该属性或后面的代码以查看视图单元格是否处于选定状态。
有一个警告:如果要在页面显示时设置默认的选定项目,则需要变得更聪明。不幸的是,Xamarin Forms没有页面Displayed事件,我们只有Appearing事件,这对于设置默认值还为时过早:绑定将不被执行。因此,请稍加延迟:
protected override async void OnAppearing() {
base.OnAppearing();
Device.BeginInvokeOnMainThread(async () => {
await Task.Delay(100);
ListViewName.SelectedItem = ...;
});
}