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

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

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

133
在Android中,只需在资源文件夹下的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>

6
问题仅在 Android 上发生,所以这对我来说是最好/最干净的解决方案。 - Ateik
2
对我来说,这也是最简单的解决方案。很可惜 Xamarin Forms 没有可自定义的属性。 - Marin Shalamanov
如果主项目在PCL上,这个解决方案会起作用吗? - Pxaml
2
我在我的 Android 项目中创建了 Style.xml 文件,但是它对我来说不起作用。我需要添加其他内容吗?也许是特殊的主题?我正在使用 Xamarin.Forms。 - Denis Vitez
很遗憾对我没用。问题中的颜色是当项目被“轻触”时显示的,我相信这不同于之后当它被选中时的颜色。这个解决方案在我选中项目(轻触后)上有效,但在“轻触”期间,我得到与问题中图片中的褪色粉色相同的颜色。 - cineam mispelt

68

看起来实际上有一种跨平台的方法可以在iOS和Android上实现这个,不确定是否适用于Windows(可能性较小)。它只使用绑定并且不需要自定义渲染器(这似乎很罕见)。这是许多谷歌搜索的混合物,因此感谢任何我可能借鉴的人……

我假设使用ViewCells,但对于文本或图像单元格也应该适用。在您的页面上,请执行以下操作:

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

1
这个可以工作 - 至少有一点。我发现当选择一个项目时,这个可以工作,但是当我选择另一个项目时,第一个项目会回到“旧”的背景 - 除非我滚动以隐藏它,然后再次滚动以显示它,在这种情况下,重新绘制时使用“新”的颜色。 - Unsliced
2
哇,谢谢!我也想到过这个方法,但是当我尝试的时候它没有起作用。我不确定我做错了什么,但是复制你的代码就可以了。 - SpaceMonkey
5
不工作,你只是在行上绘制悬停,旧颜色仍然存在,并且在底部仍可看到该颜色的1像素行。 - animekun
5
ListView.SeparatorVisibility = SeparatorVisibility.None;ListView.SeparatorColor= Color.Transparent; 这两行代码可以解决你提到的问题。 - Rohit Vipin Mathews
1
如果您正在遵循MVVM模式,这实际上不是最佳方法。 Color 实际上与模型本身没有真正的关系 - 它是一个严格的UI问题,并且依赖于框架(在此情况下为Forms)。在XAML中使用转换器或样式触发器来设置模型中的 IsSelected 标志会更具可移植性的解决方案。 - Marek
显示剩余6条评论

36

iOS

解决方案:

在自定义的 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

解决方案:

我在Android上发现的解决方案有点复杂:

  1. Resources>drawable文件夹中创建一个名为ViewCellBackground.xml的新可绘制对象:

    <?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元素的默认状态和“按下”状态的不同颜色的实心形状。

  2. 为您的的

  3. public class TouchableStackLayout: StackLayout
    {
    }
    
  4. 为该类实现自定义渲染器,设置背景资源:

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

结果:


如果为现有的UIView设置背景颜色无效,请在设置其背景颜色后调用单元格的SetNeedsLayout方法(或者叫其他名字的方法)。UITableView尽可能避免执行布局。 - Sten Petrov
@StenPetrov:不,SetNeedsDisplaySetNeedsLayout不起作用。但这并不重要,因为分配new UIView {...}是一个相当简短的解决方法。 - Falko
如何更改字体颜色? - Edgar
在Android上,我选择的颜色没有被保留。我尝试使用android:state_selected.. :[ - masterwok
SelectedBackgroundView 用于选择,而不是高亮。问题是关于高亮。 - Softlion

33

要更改所选 ViewCell 的颜色,有一个简单的过程可以不使用自定义渲染器。将您的 ViewCellTapped 事件设置如下:

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

在你的ContentPage顶部这样声明lastCellViewCell lastCell;


1
最佳解决方案!谢谢! - a.tarasevich
4
这个解决方案应该获得最多的投票并且必须排在最前面,因为这是最简单的一个。 - Vikas Lalwani
2
最佳解决方案,最简单、最美观。应该位于顶部。 - Tornike Gomareli
3
只有在从菜单中选择一个项目时,它才起作用。第一次显示菜单时它不起作用。 - Luis Lema

19

仅适用于Android

将其添加到您的自定义主题或默认主题中,在ProjectName.Android/Resources/values/styles.xml下。

<item name="android:colorActivatedHighlight">@android:color/transparent</item>

14

我有一个类似的流程,完全跨平台,但我自己跟踪选择状态,并且我是在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;
}

这也适用于上下文操作菜单吗?例如在Android上长按和在iOS上向左滑动? - mr5
我无法调用list.First(...)。它显示IEnumerable不包含First的定义... - Prince
@Prince - 你需要在代码顶部加入 using System.Linq;。 - Adam
如果你正在使用System.Linq,那么IEnumerable肯定有扩展.First()。如果你无法解决这个问题,请在StackOverflow上提出另一个问题以调试该特定问题。 - Adam
由于所选项目发生更改,它再次触发了“ItemSelected”事件,因此在我的情况下,进一步的代码遇到了异常。 - Morse
保持视图和模型分离的清晰方式是在项 VM 中添加 bool IsSelected 属性。然后在视图中,使用 IValueConverter 在 ItemTemplate 中将 isSelected 映射到所需的颜色。 - ToolmakerSteve

13

我遇到了同样的问题,像Falko建议的那样为iOS创建了自定义渲染器来解决它,但是我避免了修改Android的样式,我想出了一种方法也为Android创建了自定义渲染器。

Android视图单元格中选择标志始终为false,这有点奇怪,所以我不得不创建一个新的私有属性来跟踪它。但除此之外,如果你想为两个平台使用自定义渲染器,我认为这遵循了一种更合适的模式,在我的情况下,我是针对TextCell做的,但我相信其他CellViews也可以同样适用。

Xamarin Forms

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

安卓(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);
            }
        }
    }
}

然后,在.xaml页面中,您需要添加一个XMLNS引用,指回新的CustomViewCell...

xmlns:customuicontrols="clr-namespace:MyMobileApp.CustomUIControls"

别忘了在你的XAML中实际使用新的自定义控件。


非常感谢,这节省了我好几个小时! 我可以确认它适用于ViewCells(这正是我所需要的) - wislon
2
有人在为ListView的SelectedItem设置默认值时使其工作了吗? - Filipe Silva
这对使用RetainElement缓存策略的ListViews非常有效,但对于使用RecycleElement或RecycleElementAndDataTemplate策略的ListViews而言,IsSelected属性永远不会改变 - 但我们确实会获得'BindingContext'和'Index'属性更改。我仍在努力弄清楚是否可以以某种方式使用它们。就快成功了! :) - wislon
1
@Extragorey,你可能忘记了调用ExportRenderer方法:[assembly: ExportRenderer(typeof(ExtendedViewCell), typeof(ExtendedViewCellRenderer))] - thomasgalliker
1
我曾经使用过这种技术,但必须承认像@FilipeSilva所说的那样,如果所选项目在视图模型中初始化,则无法正常工作。只有在用户交互时才能正常工作。我尝试了解决方法,但没有找到解决办法。 - Nk54
显示剩余3条评论

7
这里提供一种纯跨平台且简洁的方法: 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"

注意:由于您的项目没有处于选中模式,所以选择样式将不会应用于任何平台。


这怎么帮助“设置选定项的高亮/背景颜色”?看起来你所做的是抑制任何颜色变化。 - ToolmakerSteve
1
它无论如何都不会改变背景颜色。但当用户想要抑制项目选择时,这是有帮助的。通常在用户想要在ListView中点击一个项目并启动详细视图,但又想抑制所选项目时,这是有帮助的。 - zafar

2

我使用类似于@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转换器保存lastitemselected,并更新背景颜色。
    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 Forms地图上的列表视图提取的位置。我希望这个解决方案对某些人有用。


1

我发现这个很棒的选项,使用效果请点击这里

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

安卓:

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

3
我认为这并没有回答问题,因为你没有设置所选项目的颜色。你只是避免了选择。 - Falko
这不仅没有回答问题,而且使选择行变得不可能! - ToolmakerSteve
公正的观点,我一定是误读了问题,因为我在寻找一种方法来删除所选行的颜色时找到了这里。我仍然可以通过轻拍手势选择一行,如果我记得正确的话。 - Paul Charlton
尝试过了:当这个效果激活时,无法再选择一行。回滚更改... - thomasgalliker

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