it-swarm.com.ru

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

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

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

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

39
Falko

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

Результат:

Заметка:

В Xamarin.Forms представляется важным создать newUIView, а не просто установить цвет фона текущего.


Android

Решение:

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

  1. Создайте новый рисуемый 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>
    

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

  2. Используйте унаследованный класс для View вашего ViewCell, например:

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

Результат:

21
Falko

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

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

Похоже, что на самом деле существует кроссплатформенный способ сделать это, работающий на iOS и Android (не уверен в Windows). Он использует только привязку и не требует пользовательских средств визуализации (что кажется редким). Это смесь большого количества поисковика, так что спасибо всем, кого я, возможно, позаимствовал у ... 

Я предполагаю ViewCells, но это должно работать и для ячеек Text или Image. Я включаю здесь только соответствующий код, помимо типичного текста, изображения и т.д.

На вашей странице сделайте что-то вроде этого:

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 и Android, если кто-то хочет поднять мне 10 очков, чтобы я мог их опубликовать :)

58
Barry Sohl

У меня похожий процесс, полностью кроссплатформенный, однако я сам отслеживаю статус выбора и сделал это в 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
            }
        }
9
Adam Pedley

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

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

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

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

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"

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

5
Zafar Ameem

Чтобы установить цвет выделенного элемента, вам нужно установить цвет 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 вы можете добавить этот стиль в ваши 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>
2
Sawan Kumar Bundelkhandi

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

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"));
2
Paul Charlton

Чтобы изменить цвет выбранного 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;
    }
} 

Объявите lastCell в верхней части вашего ContentPage как этот ViewCell lastCell;

1
CGPA6.4

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

Ниже приведено решение, которое работает даже с нестандартной стратегией cachingStrategy. Я предпочитаю это другим решениям, таким как наличие кода в методе 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>

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

Примечание: вы можете столкнуться с некоторыми проблемами из-за того, что вы заменяете делегата по умолчанию, для получения дополнительной информации об этом см. Установка делегата управления в пользовательском рендерере приводит к потере функциональности . В моем проекте все работает как надо, если я делаю это:

  • Используйте обычный ListView вместе с кодом ListItemViewCellRenderer, приведенным в предыдущих публикациях в этой теме, для ListViews, которые используют стратегию кэширования по умолчанию ListViewCachingStrategy.RetainElement.

  • Используйте этот ListView2 вместе для ListViews, которые используют стратегию кэширования не по умолчанию, т.е. ListViewCachingStrategy.RecycleElement или ListViewCachingStrategy.RecycleElementAndDataTemplate.

Я также подаю запрос на добавление функции с помощью Xamarin, пожалуйста, добавьте его, если считаете, что он должен быть добавлен к стандартному ListView: ListView крайне нуждается в свойстве SelectedItemBackgroundColor

1
user2935274

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

1
Luigino De Togni

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

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

1
Damien Doumer

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

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 = ...;
  });
}
0
Gábor