Xamarin.Formsを使用して、選択/タップされたListViewアイテムのハイライト/背景色を定義するにはどうすればよいですか?
(私のリストの背景は黒で、テキストの色は白なので、iOSのデフォルトのハイライト色は明るすぎます。対照的に、Androidでは、ハイライトはまったくありません-微妙な水平灰色線まで。)
例:(左:iOS、右:Android。「Barn2」を押しながら)
解決策:
カスタム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で見つけた解決策はもう少し複雑です:
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要素のデフォルト状態と「押された」状態に対して、異なる色のソリッドシェイプを定義します。
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);
}
}
結果:
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を想定していますが、これは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 );
}
同様のプロセスがあり、完全にクロスプラットフォームですが、選択ステータスを自分で追跡し、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
}
}
私はこれと同じ問題を抱えており、Falkoが提案するようにiOS用のカスタムレンダラーを作成することでそれを解決しましたが、Androidのスタイル変更を避け、Androidにカスタムレンダラーを使用する方法を見つけました同じように。
選択されたフラグがAndroidビューセルに対して常にfalseであるのは、ちょっとファンキーです。そのため、新しいプライベートプロパティを作成して追跡する必要がありました。それ以外は、両方のプラットフォームでカスタムレンダラーを使用する場合、これはより適切なパターンに従うと思います。私の場合、TextCellでそれを行いましたが、他のCellViewsでも同じように適用されると思います。
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で新しいカスタムコントロールを実際に使用することを忘れないでください。
純粋にクロスプラットフォームでありきちんとした方法です:
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"
を追加することを忘れないでください
注:アイテムはどれも選択モードになっていないため、選択スタイリングはどちらのプラットフォームにも適用されません。
選択した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;
強調表示されたアイテムの色を設定するには、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>
エフェクト here を使用してこの素敵なオプションを見つけました。
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のみ
カスタムテーマを追加する
<item name="Android:colorActivatedHighlight">@Android:color/transparent</item>
このソリューションは正常に機能しますが、ListViewのキャッシュ戦略をデフォルト値から変更すると、機能しなくなります。 ListViewを次のように更新すると機能します:listView = new ListView() { ... };
ただし、これを実行しても機能しません(選択したアイテムの背景は灰色のままです):listView = new ListView(cachingStrategy:ListViewCachingStrategy.RecycleElement) { ... };
以下は、非標準のcacheStrategyでも機能するソリューションです。 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を使用するListViewsについては、このスレッドに関する以前の投稿で提供されたListItemViewCellRendererコードとともに通常のListViewを使用します。
このListView2は、デフォルト以外のキャッシュ戦略、つまりListViewCachingStrategy.RecycleElementまたはListViewCachingStrategy.RecycleElementAndDataTemplateを使用するListViewで一緒に使用します。
また、Xamarinで機能リクエストを提出しています。これを標準のListViewに追加する必要があると感じた場合は、賛成してください。 ListViewにはSelectedItemBackgroundColorプロパティが必要です
私は@ 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コンバーターによって選択されたlastitemを保存し、背景色を更新します
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(同じコンテンツページ)にある場所のリストビューによって抽出されます。このソリューションが誰かに役立つことを願っています
Androidでこれを実現する最も簡単な方法は、カスタムスタイルに次のコードを追加することです。
@Android:color/transparent
前の回答は、カスタムレンダラーを提案するか、データオブジェクトなどで選択したアイテムを追跡する必要があります。これは必ずしも必要ではありません。プラットフォームに依存しない方法で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();
}
このコンバーターを使用して2つを接続します。
<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またはコードビハインドで使用して、ビューセルが選択状態にあるかどうかを確認できます。
警告が1つだけあります。ページが表示されるときにデフォルトの選択項目を設定する場合は、少し賢くする必要があります。残念ながら、Xamarin Formsにはpage Displayedイベントがなく、Appearingしかありません。これはデフォルトを設定するには早すぎます。その場合、バインディングは実行されません。そのため、少し遅れてください:
protected override async void OnAppearing() {
base.OnAppearing();
Device.BeginInvokeOnMainThread(async () => {
await Task.Delay(100);
ListViewName.SelectedItem = ...;
});
}