Xamarin Forms: Set Background Color of Item Selected in ListView from ViewModel

狂风中的少年 提交于 2020-07-09 19:33:11

问题


I am trying to change the background color of a selected item in a list view when the selection is done in code from the ViewModel. I have found a number of posts associated to changing the background color when the item is selected by user action (i.e., Tapped)

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

This solution - Using Item Taped works for when the user taps the item in the list but there are cases when an item in the ListView is selected by the user entering data elsewhere in the form.

I tried adding a handler for the ItemSelected on the ListView

    private void ListView_OnItemSelected(object sender, SelectedItemChangedEventArgs e)
    {
        if ( e.SelectedItem == null ) return;  //The selection is set to null in some cases
        if ( !(sender is ListView listView) ) return;
        if ( !(listView.TemplatedItems[0] is ViewCell cell) ) return;

        SetCellColor(cell);
    }

    private void SetCellColor(ViewCell cell)
    {
        if ( _selectedCell != null )
        {
            _selectedCell.View.BackgroundColor = Color.Transparent;
        }

        cell.View.BackgroundColor = Color.LightGray;
        _selectedCell = cell;
    }

Which seems like it should work but the TemplatedItems collection is an internal Forms object and does not work as I would expect. I do get a ViewCell returned but it does not change the background color.

Looked into doing something with the OnAppearing event of the CustomViewCell but don't see how one would know what is selected in this case. I have also played around with some approaches using renderers but these also seem to not give a way to know an item is selected.

Thanks in advance -Joe


回答1:


This can not work in Xamarin Forms , you need to use Native Method to do .

You can Custom a ViewCellRenderer to implement it .About Customizing a ViewCell , you can refer to this document .

Create a NativeCell inherits from ViewCell in Xamarin Forms .Also can add other properties inside the ViewCell .

public class NativeCell : ViewCell
{
    public static readonly BindableProperty NameProperty =
        BindableProperty.Create("Name", typeof(string), typeof(NativeCell), "");

    public string Name
    {
        get { return (string)GetValue(NameProperty); }
        set { SetValue(NameProperty, value); }
    }
}

Used in Xaml :

<ListView x:Name="listview"
                        MinimumHeightRequest="96"
                        Margin="16,0">
    <ListView.ItemTemplate>
        <DataTemplate>
            <app19:NativeCell Name="{Binding Name}"/>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Adding DataSource for ListView :

listview.ItemsSource = DataSource.GetList();

I will not show the code of DataSource , just contains the Name Property and its default data .

Finally, in iOS, you need to add the NativeViewCellRenderer inherits from ViewCellRenderer :

public class NativeViewCellRenderer : ViewCellRenderer
{
    public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
    {
        var nativeCell = (NativeCell)item;

        UITableViewCell cell = new UITableViewCell();

        cell.TextLabel.Text = nativeCell.Name;
        cell.SelectedBackgroundView = new UIView
        {
            BackgroundColor = UIColor.Blue, //Set the selected color is Blue
        };
        return cell;
    }
}

Now you will see the effect :

And the same with iOS in Android , you need to add the NativeViewCellRenderer inherits from ViewCellRenderer :

class NativeViewCellRenderer : ViewCellRenderer
{
    private Android.Views.View _cellCore;
    private Drawable _unselectedBackground;
    private bool _selected = false;
    protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView, ViewGroup parent, Context context)
    {
        _cellCore = (context as Activity).LayoutInflater.Inflate(Resource.Layout.NativeAndroidCell, null);
        TextView nameTextView = _cellCore.FindViewById<TextView>(Resource.Id.NameText);

        var nativeCell = (NativeCell)item;
        nameTextView.Text = nativeCell.Name;

        _selected = false;
        _unselectedBackground = _cellCore.Background;
        return _cellCore;
    }

    protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnCellPropertyChanged(sender, e);
        if (e.PropertyName == "IsSelected")
        {
            _selected = !_selected;
            if (_selected)
            {
                //var extendedViewCell = sender as CustomViewCell;
                _cellCore.SetBackgroundColor(Android.Graphics.Color.Red);
            }
            else
            {
                _cellCore.SetBackground(_unselectedBackground);
            }
        }
    }
}

The effect as follow :




回答2:


I did come up with a solution which I am happy with and requires no Renderer. In my custom ViewCell I have added the SelectedBackgroundColor BindableProperty as suggested.

        /// <summary>
    /// The SelectedBackgroundColor property.
    /// </summary>
    public static readonly BindableProperty SelectedBackgroundColorProperty =
        BindableProperty.Create("SelectedBackgroundColor", typeof(Color), typeof(SymbolViewCell), Color.Transparent, propertyChanged:SelectionColorChanged);

    public Color SelectedBackgroundColor
    {
        get => (Color)GetValue(SelectedBackgroundColorProperty);
        set => SetValue(SelectedBackgroundColorProperty, value);
    }

    private static void SelectionColorChanged(BindableObject bindable, object oldvalue, object newvalue)
    {
        if ( !(bindable is SymbolViewCell viewCell) ) return;

        var color = (Color) newvalue;

        viewCell.View.BackgroundColor = color;
    }

I then use a custom Converter. This is actually a generic Converter that is used elsewhere for setting values based on a true/false bound value.

public class ConfigurableBoolConverter<T> : IValueConverter
{
    public ConfigurableBoolConverter() { }

    public ConfigurableBoolConverter(T trueResult, T falseResult)
    {
        TrueResult = trueResult;
        FalseResult = falseResult;
    }

    public T TrueResult { get; set; }
    public T FalseResult { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (TrueResult == null || FalseResult == null) return !(bool)value;


        return value is bool b && b ? TrueResult : FalseResult;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (TrueResult == null || FalseResult == null) return !(bool)value;

        return value is T variable && EqualityComparer<T>.Default.Equals(variable, TrueResult);
    }
}

In Xaml I define the Converter and set my True/False values to the required background colors:

<converters:ConfigurableBoolConverter x:Key="BackgroundColorConverter"
                                  x:TypeArguments="x:String"
                                  TrueResult="Color.LightGray"
                                  FalseResult="Color.Transparent"/>

Then assign the Converter to the custom ViewCell. In the custom ViewCell the SelectedBackgroundColor was set using the Converter. As a note The SymbolViewCell already existed to solve a different issue with an image that was part of the item refreshing correctly

<DataTemplate>
     <views:SymbolViewCell 
          SelectedBackgroundColor="{Binding IsChecked, Converter={StaticResource 
                BackgroundColorConverter}}"/>
</DataTemplate>

IsChecked is a property on the Item of the ItemsDataSource. The ListView already used a a collection of Item objects and this object already had an IsChecked property.

Stripping down the Item object to the bare minimum (BindableBase implements the IPropertyChanged interface):

public class SymbolItem : BindableBase
{
    private bool? _isChecked;


    public SymbolItem(LegendInfo legendInfo, FeatureTemplate featureTemplate, ArcGISFeatureTable featureTable, IEnumerable<string> requiredFields)
    {
        IsChecked = false;
    }



    public bool? IsChecked
    {
        get => _isChecked;
        set => SetProperty(ref _isChecked, value);
    }
}

This solution would not work if the ItemsDataSource was a collection of string objects because it does require the additional property and you would need a bound SelectedItem property as a place to trigger the change in IsChecked property. But one could make a simple object with a name and IsChecked property to bind. Personally I think this added code is a lot simpler than writing a Renderer to handle things.

public SymbolItem SelectedSymbolItem
{
    get => _selectedSymbolItem;
    set
    {
        if ( _selectedSymbolItem != null ) _selectedSymbolItem.IsChecked = false;
        SetProperty(ref _selectedSymbolItem, value);
    }
}


来源:https://stackoverflow.com/questions/59671543/xamarin-forms-set-background-color-of-item-selected-in-listview-from-viewmodel

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!