Xamarin.Forms ListView:设置选中项的高亮颜色

86
使用Xamarin.Forms,我该如何定义所选/点击的ListView项的突出显示/背景颜色? (我的列表具有黑色背景和白色文字颜色,因此iOS上默认的高亮颜色太亮了。相比之下,在Android上根本没有高亮-只有一个微妙的水平灰线。) 举个例子:(左侧为iOS,右侧为Android;当按下“Barn2”时)

现在,使用VisualStateManager更改所选项的背景颜色是一种替代方法。该文章称它也适用于ListView。 - ToolmakerSteve
16个回答

1
为了设置突出显示项的颜色,您需要在iOS中设置cell.SelectionStyle的颜色。 此示例是将点击的项目颜色设置为透明。 如果您希望,可以使用UITableViewCellSelectionStyle中的其他颜色进行更改。这需要在Forms项目中创建一个新的自定义ListView渲染器,并在平台项目中编写。
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>

1
那怎么回答问题呢?你根本没有设置颜色。虽然iOS上的原始高亮颜色太亮了,但我不想完全隐藏高亮。 - Falko
@Falko - 这是设置颜色的示例,在所示的示例中,我已将其设置为透明,但您可以设置任何您喜欢的颜色。 - Sawan Kumar Bundelkhandi
我不得不使用这个iOS自定义渲染器,除了Barry Sohl的解决方案之外,以防止我的模板化列表视图将模板中所有控件的背景颜色更改为绑定的背景颜色。然而,我确实不得不将e.PropertyName == "ItemsSource"更改为e.PropertyName == "SelectedItem"才能使其正常工作。 - Scuzzlebutt
正如Falko所提到的,这在iOS上不能充分控制颜色UITableViewCellSelectionStyle仅提到了两种内置的颜色,蓝色和灰色。 - ToolmakerSteve

1
这个解决方案很好,但是如果你将ListView的缓存策略从默认值更改为其他值,它就会停止工作。如果你像这样新建一个ListView,它就能正常工作:listView = new ListView() { ... };但是如果你这样做,它就无法工作(所选项目的背景仍然是灰色的):listView = new ListView(cachingStrategy:ListViewCachingStrategy.RecycleElement) { ... }; 下面是一个即使使用非标准的cachingStrategy也能正常工作的解决方案。我更喜欢这种解决方案,而不是在OnItemSelected方法中编写代码并将背景颜色与ViewModel进行绑定。 感谢@Lang_tu_bi_dien在这里发布了这个想法:Listview Selected Item Background Color 最终代码如下: 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的ListViews,请使用普通的ListView和此线程中提供的ListItemViewCellRenderer代码。

  • 对于使用非默认缓存策略(即ListViewCachingStrategy.RecycleElement或ListViewCachingStrategy.RecycleElementAndDataTemplate)的ListViews,请使用此ListView2。

我还向Xamarin提交了一个功能请求,请投票支持它,如果您认为应将其添加到标准ListView中:ListView迫切需要SelectedItemBackgroundColor属性


1
在安卓上实现这一点最简单的方法是将以下代码添加到您的自定义样式中:

@android:color/transparent


0

改变选择颜色的最简单方法是将以下内容添加到您的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>

在添加新答案之前,请仔细检查现有的答案。这个问题在多年前已经在得票最高的答案中得到了解决:https://dev59.com/GF8e5IYBdhLWcg3wS4-5#38457882。 - ToolmakerSteve

0
之前的答案要么建议使用自定义渲染器,要么要求您在数据对象或其他地方跟踪所选项目。这并不是真正必需的,有一种方法可以以平台无关的方式链接到ListView的功能。然后可以使用它来根据需要以任何方式更改所选项目。可以修改颜色,根据所选状态显示或隐藏单元格的不同部分。 让我们向我们的ViewCell添加一个IsSelected属性。无需将其添加到数据对象中;listview选择单元格而不是绑定数据。
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没有页面显示事件,我们只有出现事件,这对于设置默认值来说太早了:绑定不会被执行。所以,使用一点延迟:

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

  Device.BeginInvokeOnMainThread(async () => {
    await Task.Delay(100);
    ListViewName.SelectedItem = ...;
  });
}

永远不要在生产软件中使用此类代码。它可能在您的计算机上运行,但每当您使用“一些延迟等待一下”时,这就是问题的指标。如果在非常慢的 Nokia 3 Android 手机上 100ms 不足够,那该怎么办? - thomasgalliker
此外,ListView的名称和ItemTemplate之间存在直接引用,这意味着您永远无法将DataTemplate提取到单独的文件中。如果DataTemplate变得很大,您最终会得到巨大的xaml文件(就像我们多次遇到的那样)。 - thomasgalliker
我一年多以前就放弃了Xamarin,转向Flutter,并且从未回头。不过,在回答这个问题的时候,这可能是唯一的解决方案。如果现在有更好的解决方案,请编辑或者最好提供一个新的答案。 - Gábor
FWIW关于@thomasgalliker的“永远不要在生产软件中使用这样的代码。”我已经使用XForms多年,并在许多方面将其推向了极限。不幸的是,仍然缺乏一个OnAppeared方法,需要每个平台的自定义渲染器来实现这样的方法,或者正如答案中所示,需要延迟操作的hack。我同意延迟hack很糟糕;事实上,即使在快速设备上,对于更复杂的页面,它也必须增加!(希望Maui最终能够添加OnAppeared。) - ToolmakerSteve

0
在iOS中,您可以添加一个新的ListViewRender来重写默认的selectedBackgroundView的backgroundColor值:
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
                        };
                    }
                }
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,