Подтвердить что ты не робот

Xamarin.Forms ListView: установка цвета подсветки элемента

Использование Xamarin.Forms, как я могу определить цвет выделения/фона для выбранного/постученного элемента ListView?

(Мой список имеет черный фон и белый цвет текста, поэтому цвет подсветки по умолчанию на iOS слишком яркий. Напротив, на Android нет подсветки вообще - до тонкой горизонтальной серой линии.)

Пример: (слева: iOS, справа: Android, при нажатии "Barn2" )

2VSCB.pngr8xlt.png

4b9b3361

Ответ 1

IOS

Решение:

В пользовательском ViewCellRenderer вы можете установить SelectedBackgroundView. Просто создайте новый UIView с фоновым цветом по вашему выбору, и вы установите.

public override UITableViewCell GetCell(Cell item, UITableView tv)
{
    var cell = base.GetCell(item, tv);

    cell.SelectedBackgroundView = new UIView {
        BackgroundColor = UIColor.DarkGray,
    };

    return cell;
}

Результат:

eRHYY.png

Примечание:

С Xamarin.Forms, кажется, важно создать новый UIView, а не просто установить цвет фона текущего.


Android

Решение:

Решение, которое я нашел на Android, немного сложнее:

  • Создайте новый drawable ViewCellBackground.xml в папке Resources > 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>
    

    Он определяет сплошные формы с разными цветами для состояния по умолчанию и "нажатого" состояния элемента пользовательского интерфейса.

  • Используйте унаследованный класс для 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);
        }
    }
    

Результат:

qMOYb.png

Ответ 2

В Android просто отредактируйте ваш файл styles.xml в разделе Resources\values, добавив следующее:

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

Ответ 3

Похоже, что на самом деле существует кроссплатформенный способ сделать это, работающий как на 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 );
}

Ответ 4

У меня есть аналогичный процесс, полностью перекрестная платформа, однако я отслеживаю статус выбора самостоятельно, и я сделал это в 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
            {
                return _selected;
            }
            set
            {
                _selected = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("BackgroundColor"));
            }
        }                 

        public Color BackgroundColor
        {
            get
            {
                if (Selected)
                    return Color.Black;
                else
                    return Color.Blue
            }
        }

Ответ 5

У меня была та же проблема, и я решил ее, создав специальный рендерер для iOS, как предлагает Фалько, однако я избежал модификации стилей для Android и нашел способ использовать собственный рендерер для Android.

Довольно забавно, что выбранный флаг всегда ложен для ячейки просмотра Android, поэтому мне пришлось создать новое частное свойство для его отслеживания. но кроме этого, я думаю, что это следует более подходящему шаблону, если вы хотите использовать пользовательские средства визуализации для обеих платформ. В моем случае я сделал это для TextCell, но я полагаю, что он применяется аналогичным образом для других CellView.

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.

Ответ 6

Вот чисто перекрестная платформа и аккуратный способ:

1) Определить действие триггера

namespace CustomTriggers {
   public class DeselectListViewItemAction:TriggerAction<ListView> {
       protected override void Invoke(ListView sender) {
                sender.SelectedItem = null;
       }
   }
}

2) Примените указанный экземпляр класса как действие EventTrigger в XAML, как показано ниже

 <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"

Примечание. Поскольку ни один из ваших элементов не находится в выбранном режиме, стилизация выбора не будет применяться ни на одной из платформ.

Ответ 7

Чтобы установить цвет выделенного элемента, вам нужно установить цвет cell.SelectionStyle в iOS.

В этом примере нужно установить цвет прозрачного элемента.

Если вы хотите, вы можете изменить его другими цветами с UITableViewCellSelectionStyle. Это должно быть написано в платформенном проекте iOS, создав новый визуализатор Custom ListView в вашем проекте Forms.

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 вы можете добавить этот стиль в свои значения /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>

Ответ 8

Нашел этот прекрасный вариант, используя эффекты здесь.

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

Ответ 9

Это решение работает отлично, но если вы измените стратегию кеширования ListView вдали от значения по умолчанию, она перестанет работать. Он работает, если вы обновите свой ListView следующим образом: listView = new ListView() { ... }; Но если вы это сделаете, это не сработает (фон остается серым для выбранного элемента): listView = new ListView(cachingStrategy:ListViewCachingStrategy.RecycleElement) { ... };

Ниже приведено решение, которое работает даже с нестандартной кешированиемStrategy. Я предпочитаю это другим решениям, например, иметь код в методе OnItemSelected в сочетании с привязкой из ViewModel для цвета фона.

Кредит @Lang_tu_bi_dien, который разместил эту идею здесь: Цвет списка избранных элементов списка

Окончательный код выглядит следующим образом:

Код 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>

Ответ 10

У меня есть & решение, похожее на @adam-pedley. Нет пользовательских средств рендеринга, в xaml я привязываю фон ViewCell Property

                <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 (та же страница контента). Я надеюсь, что это решение будет полезно для кого-то

Ответ 11

Самый простой способ сделать это на Android - добавить следующий код в ваш собственный стиль:

@android: Цвет/прозрачный

Ответ 12

Чтобы изменить цвет выбранного ViewCell, существует простой процесс без использования пользовательского средства визуализации. Сделайте событие Tapped вашего ViewCell как 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;

Ответ 13

Предыдущие ответы либо предлагают пользовательские средства визуализации, либо требуют, чтобы вы отслеживали выбранный элемент либо в ваших объектах данных, либо иным образом. Это на самом деле не требуется, есть способ связать функционирование 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 = ...;
  });
}

Ответ 14

Только для Android

Добавить в свою тему

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