【Xamarin.Forms】ListView の CustomCell を XAML で定義する

アプリを作っていると、リスト形式で何かを表示したいことってよくあると思います。
で、そのリスト1件1件のセルのレイアウトは自分で定義したい場合が多いんじゃないかと思います。

Xamarin.Formsでは、そのような仕組みを`ListView`と`CustomCell`で実現するのですが、この`CustomCell`をC#コードではなくXAMLで定義する場合の書き方が簡潔にまとまっているページがあまり見つからなかったため、ここに書いておきたいと思います。

# この記事の対象読者
* Xamarin.Formsにまだ慣れていない方
* Android/iOSアプリをネイティブで開発したことのある方

# 公式ドキュメント
Xamarin.Formsにおける`ListView`や`CustomCell`という仕組み自体は、以下の公式ドキュメントに記載されています。

* `ListView`全体の説明ページ
[Xamarin.Forms ListView](https://docs.microsoft.com/ja-jp/xamarin/xamarin-forms/user-interface/listview/)

* `CustomCell`の説明部分
[Custom Cells](https://docs.microsoft.com/ja-jp/xamarin/xamarin-forms/user-interface/listview/customizing-cell-appearance#custom-cells)

今回はここを読んでも僕がイマイチ要領を得なかった部分について書いています。

# 本文
まずは動作する最小限のソースですが、以下の通りです。

```xml:MainPage.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:local="clr-namespace:LayoutSample" 
    x:Class="LayoutSample.MainPage"
    xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core" 
    ios:Page.UseSafeArea="true">
    
    <ListView 
        x:Name="scheduleList"
        HasUnevenRows="true">        
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <StackLayout
                        HorizontalOptions="FillAndExpand"
                        Orientation="Horizontal">
                        <StackLayout
                            Orientation="Vertical">
                            <Label Text="{Binding Datestr}"/>
                            <Label Text="{Binding Title}"/>
                            <Label Text="{Binding Place}"/>
                        </StackLayout>
                        <Image
                            WidthRequest="40"
                            HeightRequest="40"
                            BackgroundColor="Olive" 
                            VerticalOptions="Center"
                            HorizontalOptions="EndAndExpand"/>
                    </StackLayout>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ContentPage>
```

```cs:MainPage.xaml.cs
using System.Collections.Generic;
using Xamarin.Forms;

namespace LayoutSample
{
    public partial class MainPage : ContentPage
    {

        private List<Schedule> ScheduleListData = new List<Schedule>();
        public MainPage()
        {
            InitializeComponent();

            // 初期データを準備
            ScheduleListData.Add(new Schedule("2019/01/03", "New Year Concert", "Orchard Hall"));
            ScheduleListData.Add(new Schedule("2019/01/07", "Shigoto hajime", "office"));
            ScheduleListData.Add(new Schedule("2019/01/20", "New Year Party", "John's place"));
            ScheduleListData.Add(new Schedule("2019/01/26", "Trip to Nagano", "Nagano"));

            // ListViewにデータソースをセット
            scheduleList.ItemsSource = ScheduleListData;
        }
    }

    // リスト1件のデータを表すクラス
    public class Schedule
    {
        public string Datestr { get; set; }
        public string Title { get; set; }
        public string Place { get; set; }

        public Schedule(string Datestr, string Title, string Place)
        {
            this.Datestr = Datestr;
            this.Title = Title;
            this.Place = Place;
        }
    }
}
```

ざっくり、XAMLでは`ListView`の配置と`CustomCell`によるセルのレイアウトの定義を、C#コードではデータクラスの定義とそのリストの生成、`ListView`へのセットなどを行なっています。

これを実行すると、以下のようなリスト（スケジュールの一覧）が表示されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/134943/aad7b16c-433e-4974-c772-df7899c3960d.png)

# ポイント
Xamarin.Formsでは`ListView`を表示するために

1. `ListView`の設置
1. セルのレイアウトテンプレートの定義
1. データソースのセット
1. 定義したレイアウトテンプレートの呼び出し
1. レイアウトテンプレートへのデータの割り当て

を行う必要があります。このあたりはAndroid、iOSのネイティブアプリ開発も同じ考え方ですね。

で、今回のように`CustomCell`をXAMLで定義する場合、C#コードでやるのは上記の中の「3. データソースのセット」だけで良い、というのがポイントです。

```cs:MainPage.xaml.cs
// ListViewにデータソースをセット
scheduleList.ItemsSource = ScheduleListData;
```

その他、「1. `ListView`の設置」、「2. セルのレイアウトテンプレートの定義」といった手順はXAMLへの記述で完了しています。

また「4. 定義したレイアウトテンプレートの呼び出し」は何かを記述する必要はありません。Xamarin.FormsがXAMLに記述した`CustomCell`を自動で呼び出してくれます。Androidのように`getView()`の中でレイアウトを`inflate`したり、iOSのように`dequeueReusableCell()`したりしなくて良いのはとても楽ですね。

「5. レイアウトテンプレートへのデータの割り当て」は、`TextCell`や`ImageCell`をセットする場合と同じ要領で行なっています。

```xml:MainPage.xaml
<Label Text="{Binding Datestr}"/>
<Label Text="{Binding Title}"/>
<Label Text="{Binding Place}"/>
```

こう書くことで、データソースである`Schedule`クラスの`Datestr`、`Title`、`Place`プロパティの値が自動的にレイアウトに割り当てられるようになっています。Bindingに指定した名前と、データクラスのプロパティ名が一致するようにだけ、注意が必要です。

# まとめ
`CustomCell`のレイアウトをXAMLで定義することで、レイアウトの定義は全てXAMLに、データの生成やセットなどの処理はC#コードに、という具合に見た目と処理をきれいに分離できます。

ググっているとC#コードでレイアウトを作っているサンプルが少なからず見つかりますが、見た目と処理はできる限り分離しておいた方が、アプリが大きくなったときや開発人数が多くなったときなどでもプロジェクト全体の見通しがよくなるため良いと思っています。

今回は最低限のコードを載せましたが、これだとデータの追加や削除など変更があっても表示内容は変わってくれないため、そのあたりは別の記事にまとめておきたいと思います。

# この記事のコード
コードはGitHubで公開しています。

[chooyan-eng/XamarinPractice](https://github.com/chooyan-eng/XamarinPractice)

今回の記事の分のコミットは以下のあたりです。（最初のコミットで全部入れてしまったため、全ファイルが変更点になってしまっています）

https://github.com/chooyan-eng/XamarinPractice/commit/ccff1f48598a8eff597f6119764ae1466295255d#diff-5bbad9995983b772cf7131ca8202c146