[Xamarin.Forms 1.4.0-pre1] ListViewの新機能 "Pull To Refresh"



## Pull To Refresh

何だかプレビュー版の記事ばかり書いている気がします。

先日、 [Xamarin.Forms 1.4.0 のプレビュー版](https://forums.xamarin.com/discussion/34359)がリリースされました。主な内容はScrollViewとListViewの機能強化です。
その中に、`ListView`のPull To Refresh(引っ張って更新するアレ)が簡単に実装できるようになった、と書かれていたので早速試してみました。

Xamarin.Forms 1.4.0のアナウンスによるとPull To Refreshに関わる変更内容は次の通りです。

```c#:
// ListView

public event EventHandler Refreshing;

public bool IsPullToRefreshEnabled { get; set; } = false;
public bool IsRefreshing { get; set; } = false;
public ICommand RefreshCommand { get; set; } = null;

public void BeginRefresh ();
public void EndRefresh ();
```

`IsPullToRefreshEnabled`に`true`をセットすることでPull To Refreshが有効になります、falseだと引っ張ってもクルクルしません。
ユーザーがListViewを引っ張ると`IsRefreshing`が`true`変更され、`Refreshing`イベントや`RefreshCommand`に登録した処理が呼ばれます。リフレッシュ処理の最後に`EndRefresh()`メソッドを呼ぶか、`IsRefreshing`を`false`に変更しなければなりません。(忘れるとずっとクルクル)

コードビハインドで書く場合は`Refreshing`イベント、ViewModelの場合は`RefreshCommand`にバインド、と使い分けると良いでしょう。

正直なところ、Pull To Refreshで`BeginRefresh()`を使う必要はありません。これは...

> It is important to note that this is the "easy" version of this API, in the future a more complete API with a standalone View may be added.

とあるように、将来追加される"より完璧なAPI"を見越して追加されたのではないかと思います。


## イベントで更新するサンプル

Viewのコードビハインドに更新処理を書く場合や、AppクラスでViewを組み立ててしまうような小規模なコードの場合は`Refreshing`イベントを使うと良いでしょう。

まずはXamarin.Formsのnugetパッケージを更新してください。検索時に「Show pre-release packages」をチェックするとプレビュー版も表示されるようになります。この辺りは[以前の記事](http://qiita.com/P3PPP/items/b4db6fa93e6ccc55fc9a#xamarinforms-%E3%81%AE%E6%9B%B4%E6%96%B0)でも解説していますので合わせてご覧ください。

`App`クラスのコンストラクタを次のように書き換えます。

```c#:App.cs
			// 追加で必要なusing
			// using System.Linq;
			// using System.Threading.Tasks;

			var listView = new ListView 
			{
				// 「引っ張って更新」を有効にする
				IsPullToRefreshEnabled = false,
				ItemsSource = Enumerable.Range (1, 10).ToList (),
			};

			var random = new Random(140);

			// 引っ張った時に呼ばれる
			listView.Refreshing += async (sender, e) => {
				// ネットワークアクセスを装ったディレイ
				await Task.Delay(1000);

				// リストビューのデータを更新
				listView.ItemsSource = 
					Enumerable.Range(1, 10)
						.Select(x => random.Next()).ToList();

				// リフレッシュ完了、どちらかを実行すれば良い
				listView.IsRefreshing = false;
//				listView.EndRefresh();
			};

			MainPage = new ContentPage {
				Content = listView,
				Padding = new Thickness(5, Device.OnPlatform(20, 0, 0), 5, 0),
			};
```

実行すると、リストを下に引っ張る度に数値が変化します。

## コマンドで更新するサンプル

MVVMでアプリケーションを実装する場合は`RefreshCommand`を使うと良いでしょう。通常、ViewModelはViewの参照を直接持たないので`IsRefreshing`や`EndRefresh()`にアクセスできません、ちょっと工夫が必要です。例えば、ViewModelに`bool`型のプロパティを設け、`IsRefreshing`プロパティと **双方向にバインド** してやると良いでしょう。

Sharedプロジェクト(またはPCLプロジェクト)に次のようなViewModelクラスを追加します。

```c#:MainPageViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Xamarin.Forms;
using System.Windows.Input;

namespace PullToRefreshSample
{
	public class MainPageViewModel : BindableObject
	{
		// ListViewのデータソース
		public ObservableCollection<Color> Colors {
			get;
			private set;
		}

		// ListView.IsRefreshingと同期させるプロパティ
		private bool isRefreshing;
		public bool IsRefreshing
		{
			get { return isRefreshing; }
			set
			{
				if (value == isRefreshing)
					return;
				isRefreshing = value;
				OnPropertyChanged ();
			}
		}

		// ListViewを引っ張った時に実行させるコマンド
		public ICommand RefreshCommand {
			get;
			private set;
		}

		public MainPageViewModel ()
		{
			Colors = new ObservableCollection<Color> {
				Color.Aqua,
				Color.Blue,
				Color.Fuchsia,
				Color.Gray,
				Color.Green,
				Color.Lime,
				Color.Maroon,
				Color.Navy,
				Color.Olive,
				Color.Pink,
			};

			var random = new Random (140);

			RefreshCommand = new Command (async (nothing) => {
				// ランダムな色に更新
				for(var i = 0; i < Colors.Count; i++)
				{
					await Task.Delay(100);
					Colors[i] = new Color(
						random.NextDouble (),
						random.NextDouble (),
						random.NextDouble ()
					);
				}

				// Binding機構経由でListViewのIsRefreshingプロパティも変更する
				IsRefreshing = false;
			},
				// ICommand.CanExecuteにもバインドしたプロパティを利用できる
				(nothing) => !IsRefreshing
			);
		}
	}
}
```

Sharedプロジェクト(またはPCLプロジェクト)にContentPageを追加、XAMLコードを次のように修正します。

```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"
			 x:Class="PullToRefreshSample.MainPage">
	<ContentPage.Content>
		<!-- IsRefreshingプロパティに双方向でバインドするのがミソ -->
		<ListView IsPullToRefreshEnabled="true"
				  SeparatorVisibility="None"
				  RefreshCommand="{Binding RefreshCommand}"
				  IsRefreshing="{Binding IsRefreshing, Mode=TwoWay}"
				  ItemsSource="{Binding Colors}">
			<ListView.ItemTemplate>
				<DataTemplate>
					<ViewCell>
						<BoxView Color="{Binding}"/>
					</ViewCell>
				</DataTemplate>
			</ListView.ItemTemplate>
		</ListView>
	</ContentPage.Content>
</ContentPage>
```

最後に`App`クラスのコンストラクタを次のように書き換えます。

```c#:App.cs
			MainPage = new MainPage {
				BindingContext = new MainPageViewModel(),
			};
```

実行するとこうなります。

![PullToRefreshSample.gif](https://qiita-image-store.s3.amazonaws.com/0/40121/b0a49c98-b987-e4a8-b086-6e55f131012f.gif "PullToRefreshSample.gif")

## まとめ

Xamarin.Forms 1.3.4の更新内容("iOS Dynamic cell sizing support")と合わせて、すぐにでもTwitterクライアントが作れそうですね！