web-dev-qa-db-fra.com

Xamarin.Forms ListView: Définit la couleur de surbrillance d'un élément tapé

Avec Xamarin.Forms, comment puis-je définir la couleur de surbrillance/d’arrière-plan d’un élément ListView sélectionné/tapé?

(Ma liste a un arrière-plan noir et une couleur de texte blanche, de sorte que la couleur de surbrillance par défaut sur iOS est trop claire. En revanche, sur Android, il n’existe aucune surbrillance - jusqu’à une subtile ligne grise horizontale.)

Exemple: (à gauche: iOS, à droite: Android; en appuyant sur "Barn2")

39
Falko

iOS

Solution:

Dans une personnalisation ViewCellRenderer, vous pouvez définir la SelectedBackgroundView. Créez simplement une nouvelle UIView avec la couleur de fond de votre choix et le tour est joué.

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

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

    return cell;
}

Résultat:

Remarque:

Avec Xamarin.Forms, il semble important de créer un newUIView plutôt que de simplement définir la couleur d'arrière-plan de celle en cours.


Android

Solution:

La solution que j'ai trouvée sur Android est un peu plus compliquée:

  1. Créez un nouveau ViewCellBackground.xml dessiné dans le dossier 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>
    

    Il définit des formes solides avec différentes couleurs pour l'état par défaut et l'état "enfoncé" d'un élément de l'interface utilisateur.

  2. Utilisez une classe héritée pour la View de votre ViewCell, par exemple:

    public class TouchableStackLayout: StackLayout
    {
    }
    
  3. Implémentez un moteur de rendu personnalisé pour cette classe en définissant la ressource d'arrière-plan:

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

Résultat:

21
Falko

Sous Android, éditez simplement votre fichier Style.xml sous Resources\Value en ajoutant ceci:

<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

On dirait qu’il existe en fait un moyen multiplateforme de faire cela qui fonctionne à la fois sur iOS et Android (pas sûr de Windows). Il utilise uniquement la liaison et ne nécessite pas de rendus personnalisés (ce qui semble rare). C'est un mélange de beaucoup de googles, alors merci à tous ceux à qui j'ai emprunté ... 

Je suppose ViewCells, mais cela devrait également fonctionner pour les cellules Texte ou Image. Je n'inclus que le code pertinent ici au-delà du texte typique, de l'image, etc.

Sur votre page, faites quelque chose comme ça:

MyModel model1 = new MyModel();
MyModel model2 = new MyModel();

ListView list = new ListView
{
    ItemsSource = new List<MyModel> { model1, model2 };
    ItemTemplate = new DataTemplate( typeof(MyCell) )
};

Votre modèle personnalisé pourrait ressembler à quelque chose comme ceci:

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

Ensuite, pour votre ItemTemplate, vous avez besoin d’une classe de cellule personnalisée qui ressemble à ceci:

public class MyCell : ViewCell
{
    public MyCell() : base()
    {
        RelativeLayout layout = new RelativeLayout();
        layout.SetBinding( Layout.BackgroundColorProperty, new Binding( "BackgroundColor" ) );

        View = layout;
    }
}

Ensuite, dans votre gestionnaire d'événements ItemSelected, procédez comme suit. Notez que 'sélectionné' est une instance de MyModel utilisée pour suivre l'élément actuellement sélectionné. Je ne montre ici que la couleur de fond, mais j’utilise également cette technique pour mettre en surbrillance en arrière le texte et détailler ses couleurs.

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

J'ai des captures d'écran iOS et Android si quelqu'un veut me faire passer à 10 points pour pouvoir les poster :)

58
Barry Sohl

J'ai un processus similaire, complètement multi-plateforme, mais je surveille moi-même le statut de la sélection et je l'ai fait dans 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>

Puis dans l'événement 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;

            };

Comme vous pouvez le constater, je règle simplement ListView.SelectedItem sur null pour supprimer les styles de sélection spécifiques à la plate-forme qui entrent en jeu.

Dans mon modèle j'ai 

        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

J'avais le même problème et je l'ai également résolu en créant un moteur de rendu personnalisé pour iOS, comme le suggère Falko. Cependant, j'ai évité la modification des styles pour Android. J'ai également trouvé un moyen d'utiliser un moteur de rendu personnalisé pour Android.

Il est un peu génial de voir que l'indicateur sélectionné est toujours faux pour la cellule d'affichage Android, c'est pourquoi j'ai dû créer une nouvelle propriété privée pour le suivre. mais à part cela, je pense que cela suit un modèle plus approprié si vous voulez utiliser des rendus personnalisés pour les deux plates-formes. Dans mon cas, je l'ai fait pour TextCell mais je pense que cela s'applique de la même manière pour les autres 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

Voici la plate-forme purement transversale et soignée:

1) Définir une action de déclenchement

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

2) Appliquez l'instance de classe ci-dessus en tant qu'action EventTrigger en XAML comme ci-dessous.

 <ListView x:Name="YourListView" ItemsSource="{Binding ViewModelItems}">
    <ListView.Triggers>
        <EventTrigger Event="ItemSelected">
            <customTriggers:DeselectListViewItemAction></customTriggers:DeselectListViewItemAction>
        </EventTrigger>
    </ListView.Triggers>
</ListView>

N'oubliez pas d'ajouter xmlns:customTriggers="clr-namespace:CustomTriggers;Assembly=ProjectAssembly"

Remarque: Comme aucun de vos éléments ne se trouve dans le mode sélectionné, le style de sélection ne sera appliqué à aucune des plates-formes.

5
Zafar Ameem

Afin de définir la couleur de l'élément en surbrillance, vous devez définir la couleur de cell.SelectionStyle dans iOS. 

Cet exemple consiste à définir la couleur de l'élément tapé sur transparent. 

Si vous le souhaitez, vous pouvez le changer avec d'autres couleurs de UITableViewCellSelectionStyle. Ceci doit être écrit dans le projet de plateforme d'iOS en créant un nouveau rendu de liste personnalisé dans votre projet de formulaire. 

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

Pour Android, vous pouvez ajouter ce style dans vos valeurs/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>

Trouvé cette belle option en utilisant des effets ici .

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

Formes:

ListView_Demo.Effects.Add(Effect.Resolve($"MyEffects.ListViewHighlightEffect"));
2
Paul Charlton

Pour changer la couleur de la variable ViewCell sélectionnée, il existe un processus simple sans utilisation du rendu personnalisé. Faites Tapped événement de votre ViewCell comme ci-dessous

<ListView.ItemTemplate>
    <DataTemplate>
        <ViewCell Tapped="ViewCell_Tapped">            
        <Label Text="{Binding StudentName}" TextColor="Black" />
        </ViewCell>
    </DataTemplate>
</ListView.ItemTemplate>

Dans votre fichier ContentPage ou .cs, implémentez l'événement

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

Déclarez lastCell en haut de votre ContentPage comme ceci ViewCell lastCell;

1
CGPA6.4

J'ai et utilise une solution similaire à @ adam-pedley . Aucun moteur de rendu personnalisé, en xaml, je lie l'arrière-plan ViewCell, propriété

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

En code (MVVM), enregistre le dernier élément sélectionné par un convertisseur boolToColor Converter

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

Mon exemple est extrait d’une liste de lieux figurant dans une Xamarin Forms Maps (même page de contenu) . J'espère que cette solution sera utile à quelqu'un.

1
Luigino De Togni

Le moyen le plus simple de réaliser cela sur Android consiste à ajouter le code suivant à votre style personnalisé: 

@Android: couleur/transparent

1
Damien Doumer

Les réponses précédentes suggèrent des rendus personnalisés ou vous obligent à garder l'élément sélectionné dans vos objets de données ou autrement. Ce n'est pas vraiment nécessaire, il existe un moyen de se connecter au fonctionnement de ListView d'une manière agnostique. Cela peut ensuite être utilisé pour modifier l'élément sélectionné de la manière requise. Les couleurs peuvent être modifiées, différentes parties de la cellule affichées ou masquées en fonction de l'état sélectionné.

Ajoutons une propriété IsSelected à notre ViewCell. Il n'est pas nécessaire de l'ajouter à l'objet de données. la listview sélectionne la cellule, pas les données liées.

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
  }

  // ...
}

Pour créer le lien manquant entre les cellules et la sélection dans la vue liste, nous avons besoin d'un convertisseur (l'idée originale vient du 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();
}

Nous connectons les deux en utilisant ce convertisseur:

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

Cette liaison relativement complexe sert à vérifier quel élément est actuellement sélectionné. Il compare la propriété SelectedItem de la vue liste à la BindingContext de la vue dans la cellule. Ce contexte de liaison est l’objet de données auquel nous sommes liés. En d'autres termes, il vérifie si l'objet de données désigné par SelectedItem est bien l'objet de données de la cellule. S'ils sont identiques, nous avons la cellule sélectionnée. Nous lions cette propriété à la propriété IsSelected qui peut ensuite être utilisée dans XAML ou dans le code suivant pour voir si la cellule de vue est dans l'état sélectionné.

Une mise en garde s'impose: si vous souhaitez définir un élément sélectionné par défaut lorsque votre page s'affiche, vous devez être un peu malin. Malheureusement, Xamarin Forms n'a pas d'événement de page affichée, nous avons seulement Apparaître et c'est trop tôt pour définir la valeur par défaut: la liaison ne sera pas exécutée à ce moment-là. Alors, utilisez un peu de retard:

protected override async void OnAppearing() {
  base.OnAppearing();

  Device.BeginInvokeOnMainThread(async () => {
    await Task.Delay(100);
    ListViewName.SelectedItem = ...;
  });
}
0
Gábor