shuhelohelo’s blog

Xamarin.Forms多めです.

ListViewの項目に対して異なるテンプレートを適用する

ListViewやCollectionViewといったコレクションをリスト表示する系のコントロールは,各項目の見た目をDataTemplateで定義します.

これによって好きな各項目を任意の見た目で表示することができます.

このDataTemplateによる見た目の指定はシンプルに使えばすべての項目に同じ見た目が適用されるのですが,任意の条件で適用するテンプレートを切り替えることもでき,より豊かな表現ができるようになっています.

今回紹介するのは複数のテンプレートを定義して,それを動的に変更する方法です.

何かしらの条件に応じて項目のテンプレートを切り替える場合,DataTemplateSelectorを使います.

docs.microsoft.com

複数のDataTemplateを用意する

切り替えるテンプレートをResourceに用意します.ContentPage.Resources 内に定義していますが,アクセスできる場所であればどこでもOkです.

ここでは2つ用意し,それぞれfirstTemplatesecondTemplateという名前をつけます.

注意したいのは,今回使うのはListViewなのでDataTemplateの中にViewCell(など)を置く必要があります.これはCollectionViewでは必要ありません.

    <ContentPage.Resources>

        <!--  First Template  -->
        <DataTemplate x:Key="firstTemplate">
            <ViewCell>
                <Grid>
                    <Label FontSize="Title" Text="FirstTemplate!" />
                    <Image
                        HeightRequest="50"
                        Source="myIcon"
                        WidthRequest="50" />
                    <Label
                        FontSize="Large"
                        HorizontalOptions="End"
                        Text="{Binding TemplateId}" />
                </Grid>
            </ViewCell>
        </DataTemplate>

        <!--  Second Template  -->
        <DataTemplate x:Key="secondTemplate">
            <ViewCell>
                <StackLayout>
                    <Label
                        FontSize="Large"
                        Text="SecondTemplate!"
                        TextColor="Red" />
                    <CheckBox />
                    <Label FontSize="Large" Text="{Binding TemplateId}" />
                </StackLayout>
            </ViewCell>
        </DataTemplate>

    </ContentPage.Resources>

1つ目のテンプレートはラベルと画像とラベルが表示され,2つ目のテンプレートはラベルとチェックボックスとラベルが縦に並ぶ,という内容になっています. データバインディングも使ってあります.

テンプレートを切り替えるロジックを書く

この2つのテンプレートを切り替える条件をC#側で用意します.

MyDataTemplateSelectorと名前をつけるとします.

    public class MyDataTemplateSelector : DataTemplateSelector
    {
        //切り替えるテンプレートを保持するプロパティを用意する
        public DataTemplate FirstTemplate { get; set; }
        public DataTemplate SecondTemplate { get; set; }

        protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
        {
            //ここに条件を書き
            //条件にマッチするプロパティ(DataTemplate)を返す
            return ((MyItem)item).TemplateId==0 ? FirstTemplate : SecondTemplate;

            //itemパラメータにはXaml側から各項目にバインドされたオブジェクト
            //今回はMyItemオブジェクトが入ってくる
            //今回はそれを利用してテンプレートを切り替える
        }
    }

ListViewにバインディングするデータは以下とします.

    public class MyItem
    {
        public int TemplateId { get; set; }
    }
        public List<MyItem> ItemList { get; set; }

        public MainPage()
        {
            InitializeComponent();

            ItemList = new List<MyItem>();
            for (int i = 0; i < 50; i++)
            {
                ItemList.Add(new MyItem
                {
                    //0と1が交互に割り当てられる
                    TemplateId = i % 2
                }); ;
            }

            this.BindingContext = this;
        }

作ったDatatemplateSelectorをXAML側で指定する

まずはテンプレートセレクターをXAML側で参照できるようにテンプレートセレクターの名前空間を追加します.

ページのトップの要素,今回の例ではContentPage,に以下の1行を追加します.

    xmlns:local="clr-namespace:XFCarouselPractice"

こんな感じになると思います.

<ContentPage
    x:Class="XFCarouselPractice.MainPage"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:d="http://xamarin.com/schemas/2014/forms/design"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:XFCarouselPractice"
    mc:Ignorable="d">

そうすると,localというプレフィクスでMyDataTemplateSelectorにアクセスできるようになります.インテリセンスにも表示されます.

f:id:shuhelohelo:20191211071815p:plain

このMyDataTemplateSelectorFirstTemplateSecondTemplateという2つのプロパティがあり,それぞれにテンプレートを割り当てます.

        <local:MyDataTemplateSelector x:Key="myDataTemplateSelector"
                                      FirstTemplate="{StaticResource firstTemplate}"
                                      SecondTemplate="{StaticResource secondTemplate}"/>

↓インテリセンスもきく. f:id:shuhelohelo:20191211072413p:plain

これで準備は完了です.XAML側はこんな感じになります.

    <ContentPage.Resources>
        
        <!--  First Template  -->
        <DataTemplate x:Key="firstTemplate">
            <ViewCell>
                <Grid>
                    <Label FontSize="Title" Text="FirstTemplate!" />
                    <Image
                        HeightRequest="50"
                        Source="myIcon"
                        WidthRequest="50" />
                    <Label
                        FontSize="Large"
                        HorizontalOptions="End"
                        Text="{Binding TemplateId}" />
                </Grid>
            </ViewCell>
        </DataTemplate>

        <!--  Second Template  -->
        <DataTemplate x:Key="secondTemplate">
            <ViewCell>
                <StackLayout>
                    <Label
                        FontSize="Large"
                        Text="SecondTemplate!"
                        TextColor="Red" />
                    <CheckBox />
                    <Label FontSize="Large" Text="{Binding TemplateId}" />
                </StackLayout>
            </ViewCell>
        </DataTemplate>

        <local:MyDataTemplateSelector
            x:Key="myDataTemplateSelector"
            FirstTemplate="{StaticResource firstTemplate}"
            SecondTemplate="{StaticResource secondTemplate}" />
    </ContentPage.Resources>

使う

ListViewにItemsSourceプロパティにItemListをバインディングして,ItemTemplateにmyDataTemplateSelector(キー名)を指定します.

    <ListView
        HasUnevenRows="True"
        ItemTemplate="{StaticResource myDataTemplateSelector}"
        ItemsSource="{Binding ItemList}" />

実行します.

f:id:shuhelohelo:20191211091812p:plain

交互にFirstTemplateとSecondTemplateの内容が表示されているのがわかると思います.

データバインドもされています.

今回のソースコード

github.com