ItemsControl は便利なコントロールなのでどんどん拡張していきたい!
というわけで、リストの背景色を交互に替える表示を ItemsControl を利用して作りたいと思います
今回のソリューションはこれまでのものを拡張して下記のようになります
スポンサードリンク
まずは DataTemplateSelector
/// <summary> /// DataTemplateSelector の基底クラス /// </summary> public class DataTemplateSelector { /// <summary> /// テンプレートを選択する /// </summary> /// <param name="item">アイテムのデータソース</param> /// <param name="container">アイテムのコンテナ</param> /// <param name="index">アイテムのインデックス</param> /// <returns>選択されたテンプレート</returns> public virtual DataTemplate SelectTemplate(object item, BindableObject container, int? index = null) { return null; } }
継承前提でメソッド1つだけの基底クラスです
これを継承した AlternateItemTemplateSelector
/// <summary> /// 交互にテンプレートを切り替える ItemTemplateSelector /// </summary> public class AlternativeItemTemplateSelector : DataTemplateSelector { /// <summary> /// 偶数行のテンプレート /// </summary> public DataTemplate DefaultTemplate { get; set; } /// <summary> /// 奇数行のテンプレート /// </summary> public DataTemplate AlternateTemplate { get; set; } /// <summary> /// テンプレートを選択する /// </summary> /// <param name="item">アイテムのデータソース</param> /// <param name="container">アイテムのコンテナ</param> /// <param name="index">アイテムのインデックス</param> /// <returns>選択されたテンプレート</returns> public override DataTemplate SelectTemplate(object item, BindableObject container, int? index = null) { if (!index.HasValue || index.Value%2 == 0 || this.AlternateTemplate == null) { return this.DefaultTemplate; } return this.AlternateTemplate; } }
アイテムの連番から偶数・奇数を判断しています・・・仮想化 UI が組めるようになったら ViewModel で判断した方がよさそうですがとりあえずこれで
次に ItemsControl はちょっと大改造
/// <summary> /// ItemsControl 風 View /// </summary> public class ItemsControl : ContentView { #region ItemsPanel /// <summary> /// ItemsPanel BindableProperty /// </summary> public static readonly BindableProperty ItemsPanelProperty = BindableProperty.Create<ItemsControl, Layout<View>>( p => p.ItemsPanel, new StackLayout(), BindingMode.OneWay, null, OnItemsPanelChanged); /// <summary> /// ItemsPanel CLR プロパティ /// </summary> public Layout<View> ItemsPanel { get { return (Layout<View>)this.GetValue(ItemsPanelProperty); } set { this.SetValue(ItemsPanelProperty, value); } } /// <summary> /// ItemsPanel 変更イベントハンドラ /// </summary> /// <param name="bindable">BindableObject</param> /// <param name="oldValue">古い値</param> /// <param name="newValue">新しい値</param> private static void OnItemsPanelChanged(BindableObject bindable, Layout<View> oldValue, Layout<View> newValue) { var control = bindable as ItemsControl; if (control == null) { return; } if (oldValue != null) { oldValue.Children.Clear(); } if (newValue == null) { return; } control.ItemsRender(); control.Content = newValue; control.UpdateChildrenLayout(); control.InvalidateLayout(); } #endregion //ItemsPanel #region ItemsSource /// <summary> /// ItemsSource BindableProperty /// </summary> public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create<ItemsControl, IEnumerable>( p => p.ItemsSource, new ObservableCollection<object>(), BindingMode.OneWay, null, OnItemsSourceChanged); /// <summary> /// ItemsSource CLR プロパティ /// </summary> public IEnumerable ItemsSource { get { return (IEnumerable)this.GetValue(ItemsSourceProperty); } set { this.SetValue(ItemsSourceProperty, value); } } /// <summary> /// ItemsSource 変更イベントハンドラ /// </summary> /// <param name="bindable">BindableObject</param> /// <param name="oldValue">古い値</param> /// <param name="newValue">新しい値</param> private static void OnItemsSourceChanged(BindableObject bindable, IEnumerable oldValue, IEnumerable newValue) { var control = bindable as ItemsControl; if (control == null) { return; } var oldCollection = oldValue as INotifyCollectionChanged; if (oldCollection != null) { oldCollection.CollectionChanged -= control.OnCollectionChanged; } if (newValue == null) { return; } control.ItemsRender(); var newCollection = newValue as INotifyCollectionChanged; if (newCollection != null) { newCollection.CollectionChanged += control.OnCollectionChanged; } control.UpdateChildrenLayout(); control.InvalidateLayout(); } #endregion //ItemsSource #region ItemTemplate /// <summary> /// ItemTemplate BindableProperty /// </summary> public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create<ItemsControl, DataTemplate>( p => p.ItemTemplate, default(DataTemplate)); /// <summary> /// ItemTemplate CLR プロパティ /// </summary> public DataTemplate ItemTemplate { get { return (DataTemplate)this.GetValue(ItemTemplateProperty); } set { this.SetValue(ItemTemplateProperty, value); } } #endregion //ItemTemplate #region ItemTemplateSelector /// <summary> /// ItemTemplate BindableProperty /// </summary> public static readonly BindableProperty ItemTemplateSelectorProperty = BindableProperty.Create<ItemsControl, DataTemplateSelector>( p => p.ItemTemplateSelector, default(DataTemplateSelector), BindingMode.OneWay, null, OnItemTemplateSelectorChanged); /// <summary> /// ItemTemplate CLR プロパティ /// </summary> public DataTemplateSelector ItemTemplateSelector { get { return (DataTemplateSelector)this.GetValue(ItemTemplateSelectorProperty); } set { this.SetValue(ItemTemplateSelectorProperty, value); } } /// <summary> /// ItemTemplateSelector 変更イベントハンドラ /// </summary> /// <param name="bindable">BindableObject</param> /// <param name="oldValue">古い値</param> /// <param name="newValue">新しい値</param> private static void OnItemTemplateSelectorChanged(BindableObject bindable, DataTemplateSelector oldValue, DataTemplateSelector newValue) { var control = bindable as ItemsControl; if (control == null) { return; } control.ItemsRender(); } #endregion //ItemTemplate /// <summary> /// コンストラクタ /// </summary> public ItemsControl() { this.Content = this.ItemsPanel; } /// <summary> /// アイテムの表示更新 /// </summary> private void ItemsRender() { this.ItemsPanel.Children.Clear(); var index = 0; foreach (var item in this.ItemsSource) { var template = this.ItemTemplateSelector != null ? this.ItemTemplateSelector.SelectTemplate(item, null, index) : this.ItemTemplate; var content = template.CreateContent(); View view; var cell = content as ViewCell; if (cell != null) { view = cell.View; } else { view = (View)content; } view.BindingContext = item; this.ItemsPanel.Children.Add(view); index++; } } /// <summary> /// Items の変更イベントハンドラ /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { this.ItemsPanel.Children.RemoveAt(e.OldStartingIndex); this.UpdateChildrenLayout(); this.InvalidateLayout(); } var collection = this.ItemsSource as ObservableCollection<object>; if (e.NewItems == null || collection == null) { return; } foreach (var item in e.NewItems) { var content = this.ItemTemplate.CreateContent(); View view; var cell = content as ViewCell; if (cell != null) { view = cell.View; } else { view = (View)content; } view.BindingContext = item; this.ItemsPanel.Children.Insert(collection.IndexOf(item), view); } this.UpdateChildrenLayout(); this.InvalidateLayout(); } }
重要なのは次の箇所です
#region ItemTemplateSelector /// <summary> /// ItemTemplate BindableProperty /// </summary> public static readonly BindableProperty ItemTemplateSelectorProperty = BindableProperty.Create<ItemsControl, DataTemplateSelector>( p => p.ItemTemplateSelector, default(DataTemplateSelector), BindingMode.OneWay, null, OnItemTemplateSelectorChanged); /// <summary> /// ItemTemplate CLR プロパティ /// </summary> public DataTemplateSelector ItemTemplateSelector { get { return (DataTemplateSelector)this.GetValue(ItemTemplateSelectorProperty); } set { this.SetValue(ItemTemplateSelectorProperty, value); } } /// <summary> /// ItemTemplateSelector 変更イベントハンドラ /// </summary> /// <param name="bindable">BindableObject</param> /// <param name="oldValue">古い値</param> /// <param name="newValue">新しい値</param> private static void OnItemTemplateSelectorChanged(BindableObject bindable, DataTemplateSelector oldValue, DataTemplateSelector newValue) { var control = bindable as ItemsControl; if (control == null) { return; } control.ItemsRender(); } #endregion //ItemTemplate /// <summary> /// アイテムの表示更新 /// </summary> private void ItemsRender() { this.ItemsPanel.Children.Clear(); var index = 0; foreach (var item in this.ItemsSource) { var template = this.ItemTemplateSelector != null ? this.ItemTemplateSelector.SelectTemplate(item, null, index) : this.ItemTemplate; var content = template.CreateContent(); View view; var cell = content as ViewCell; if (cell != null) { view = cell.View; } else { view = (View)content; } view.BindingContext = item; this.ItemsPanel.Children.Add(view); index++; } }
ItemTemplateSelector なる BindableProperty を追加して、変更されたら ItemTemplateSelector で DataTemplate を選択し直しています
ViewModel はあんまり重要じゃないので割愛して最後に XAML
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:b="clr-namespace:XamarinControl.Behaviors;assembly=XamarinControl" xmlns:c="clr-namespace:XamarinControl.Controls;assembly=XamarinControl" xmlns:s="clr-namespace:XamarinControl.Selectors;assembly=XamarinControl" xmlns:vm="clr-namespace:XamarinControl.ViewModels;assembly=XamarinControl" x:Class="XamarinControl.Views.TopPage"> <ContentPage.BindingContext> <vm:TopPageViewModel/> </ContentPage.BindingContext> <c:ItemsControl ItemsSource ="{Binding Items}"> <c:ItemsControl.ItemTemplateSelector> <s:AlternativeItemTemplateSelector> <!-- 奇数行のテンプレート --> <s:AlternativeItemTemplateSelector.DefaultTemplate> <DataTemplate> <Grid Padding="20,10" BackgroundColor="White"> <Label Text="{Binding Title}" TextColor="Black"/> </Grid> </DataTemplate> </s:AlternativeItemTemplateSelector.DefaultTemplate> <!-- 偶数行のテンプレート --> <s:AlternativeItemTemplateSelector.AlternateTemplate> <DataTemplate> <Grid Padding="20,10" BackgroundColor="#FFFF6638"> <Label Text="{Binding Title}" TextColor="Black"/> </Grid> </DataTemplate> </s:AlternativeItemTemplateSelector.AlternateTemplate> </s:AlternativeItemTemplateSelector> </c:ItemsControl.ItemTemplateSelector> </c:ItemsControl> </ContentPage>
ItemsControl と ItemTemplateSelector が頑張ってくれるので XAML の記述は簡単です
いつものように実行
紅白の交互背景色が出ました