簡體   English   中英

Xamarin.Forms ListView:設置點擊項的高亮顏色

[英]Xamarin.Forms ListView: Set the highlight color of a tapped item

使用Xamarin.Forms ,如何定義選定/點擊的 ListView 項目的突出顯示/背景顏色?

(我的列表有黑色背景和白色文本顏色,所以 iOS 上的默認突出顯示顏色太亮了。相比之下,Android 上根本沒有突出顯示 - 直到一條微妙的水平灰線。)

示例:(左:iOS,右:Android;同時按“Barn2”)

在 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,但這也適用於文本或圖像單元格。 除了典型的文本、圖像等,我只在此處包含相關代碼。

在你的頁面上做這樣的事情:

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

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而不是僅僅設置當前UIView的背景顏色似乎很重要。


安卓

解決方案:

我在 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. ViewCellView使用繼承的類,例如:

     public class TouchableStackLayout: StackLayout { }
  3. 為此類設置背景資源實現自定義渲染器:

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

結果:

要更改所選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;
    }
} 

ContentPage的頂部聲明lastCell就像這個ViewCell lastCell;

僅適用於安卓

ProjectName.Android/Resources/values/styles.xml下添加自定義主題或默認主題

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

我遇到了同樣的問題,我也按照 Falko 的建議通過為 iOS 創建自定義渲染器來解決它,但是,我避免了對 Android 的樣式修改,我還想出了一種為 Android 使用自定義渲染器的方法。

對於 android 視圖單元格,所選標志始終為 false 的方式有點奇怪,這就是為什么我必須創建一個新的私有屬性來跟蹤它。 但除此之外,如果您想為兩個平台使用自定義渲染器,我認為這遵循更合適的模式,在我的情況下,我為 TextCell 做了它,但我相信它適用於其他 CellViews 的方式相同。

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 中實際使用新的自定義控件。

這是純粹的跨平台和簡潔的方式:

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"

注意:由於您的所有項目均未處於選定模式,因此選擇樣式將不會應用於任一平台。

我有並使用了類似於@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 轉換器選擇的最后一項,我更新了背景顏色

    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 Maps(相同內容頁面)中的地點列表視圖提取的。 我希望這個解決方案對某人有用

為了設置突出顯示項的顏色,您需要在 iOS 中設置cell.SelectionStyle的顏色。

本例是將點擊項目的顏色設置為透明。

如果需要,您可以使用UITableViewCellSelectionStyle其他顏色更改它。 這將通過在您的 Forms 項目中創建一個新的自定義 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>

此解決方案工作正常,但如果您將 ListView 的緩存策略更改為遠離默認值,它將停止工作。 如果你像這樣更新你的 ListView,它會起作用: listView = new ListView() { ... }; 但是如果你這樣做它就不起作用(所選項目的背景保持灰色): listView = new ListView(cachingStrategy:ListViewCachingStrategy.RecycleElement) { ... };

下面是一個即使使用非標准緩存策略也能工作的解決方案。 與其他解決方案相比,我更喜歡這個解決方案,例如在 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 的 ListView,使用普通 ListView 和本線程之前帖子中給出的 ListItemViewCellRenderer 代碼。

  • 將此 ListView2 一起用於使用非默認緩存策略的 ListView,即 ListViewCachingStrategy.RecycleElement 或 ListViewCachingStrategy.RecycleElementAndDataTemplate。

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

安卓:

[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:顏色/透明

更改選擇顏色的最簡單方法是將這些添加到您的 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>

先前的答案要么建議自定義渲染器,要么要求您在數據對象或其他方式中跟蹤所選項目。 這並不是真正必需的,有一種方法可以以與平台無關的方式鏈接到ListView的功能。 然后可以使用它以任何需要的方式更改所選項目。 可以修改顏色,根據所選狀態顯示或隱藏單元格的不同部分。

讓我們一加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 = ...;
  });
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM