Xamarin逆引きTips
MvvmCrossでカスタムコンバーターを作成するには?
MvvmCrossでのiOS/Androidアプリ開発において、バインディングする値を変換できるカスタムコンバーターの使い方を説明する。
MvvmCrossのiOS/Androidアプリ開発では、カスタムコンバーターを用いることでバインディングする値を変換できる。今回は、そのカスタムコンバーターを作成し、iOS/Android各プラットフォームから、それを利用する方法を解説する*1。
- *1 なお、本TipsはMac OS X(10.10.3)、Xamarin Studio(5.9.1)、MvvmCross(3.5.0)で動作を確認している(※編集部注: Windows上のVisual Studioでも同様の手順で、本稿の内容が実現できることは確認している)。
コンバーターとは
Value Converter(以下、コンバーター)とは、MvvmCrossの機能の一つで、プロパティバインディング時に、その値を変換する仕組みである。ViewModelプロパティからViewプロパティへの変換と、ViewプロパティからViewModelプロパティへの変換の双方向に対応している。
コンバーターは、「ViewModelの論理的な値」と「Viewへ表示したい値」に差があるときに活躍する。例えば、ViewModelにコンテンツの投稿時刻をDateTime
型のプロパティとして保持しておき、Viewではstring
プロパティとして“12:01”のような“時:分”形式の文字列を受け取る場合などである。また、コンバーターを差し替えることで、ViewModelを変更することなく、“5/17 12:01”(月/日 時:分)などの表示へ変更することもできる。
コンバーターを利用するプロジェクトは、基本的に図1のようになる。
図1の通り、コンバーターはCoreプロジェクトに実装することも、TouchプロジェクトとDroidプロジェクト、それぞれで実装することもできる。同じデータであっても、プラットフォームごとに異なる表示をさせたり、プラットフォームごとの画像リソースを表示したりするといった対応が可能となる。
ViewModelの値を変換してViewへ渡せる仕組みであるため、コンバーターは画面作成時に活躍するが、パフォーマンスがシビアな局面では注意が必要だ。例えばiOSのUITableView
やAndroidのListView
などのリスト形式のViewは、リストアイテムとなるViewが再利用されるため、スクロールするたびにバインディングが発生する。コンバーターはバインディングの都度、実行されるため、リストスクロールの負荷が高くなることがある。
実装方針
それではコンバーターのサンプルを作成してみよう。Coreプロジェクトへ実装する(プラットフォーム間で共通の)コンバーターと、プラットフォームごとに実装するコンバーターを順に実装する。
はじめに、入力された文字数をカウントするTextCount
コンバーターを、Coreプロジェクトに実装する。
次に、入力された文字数が10文字以上であれば色付きで文字数を表示するCountColor
コンバーターを、プラットフォームごとのプロジェクトで実装する。
Coreプロジェクトコンバーターの実装
プロジェクトの作成
「Tips:MvvmCrossのプロジェクトをセットアップするには?」の手順に従い、MvvmCrossプロジェクトを作成する。ソリューション名は「CrossConverterSample」と設定する。
Coreプロジェクトの実装
FirstViewModel
クラスは、プロジェクト作成時にあらかじめ実装されているHello
プロパティを使用するため、変更の必要はない。
さっそくコンバータークラスを作成する。CrossConverterSample.Core
プロジェクトのルートディレクトリへConverters
ディレクトリを作成し、その下へMvxTextCountConverter.cs
ファイルを作成する(図2)。
MvxTextCountConverter
クラスは次のように実装する。
using System;
using Cirrious.CrossCore.Converters;
namespace CrossConverterSample.Core.Converters
{
public class MvxTextCountConverter : MvxValueConverter<string, string>
{
protected override string Convert (string value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int count = value.Length;
return string.Format ("{0} count", count);
}
}
}
|
コンバータークラスはMvvmCrossに用意されているIMvxValueConverter
インターフェースを実装する。Convert
メソッドにより、ViewModelからViewへ値が渡されるときのコンバート処理を実装し、ConvertBack
メソッドにより、ViewからViewModelへ値が渡されるときのコンバート処理を実装する。
ただし、IMvxValueConverter
インターフェースではConvert
メソッドとConvertBack
メソッドの引数はobject
型であり、メソッド内でキャストが必要となる。バインドされる型が限定されている場合には、MvxValueConverter
クラスを型クラス指定ありで継承することで引数の型を限定できる。今回は、string
型からstring
型へのコンバーターとして実装している。
MvxTextCountConverter
はViewModelからViewへの一方向のバインディングであるため、Convert
メソッドのみ実装している。このコンバーターは渡されたプロパティの文字数を"{0} count"
のフォーマットで返す。
コンバータークラス名について
MvvmCrossはアプリ起動時に、IMvxValueConverter
を継承したクラスをコンバータークラスとして列挙する。このとき、コンバータークラス名は次のようなものとなる。
- TextCount
- TextCountValueConverter
- TextCountConverter
- MvxTextCountValueConverter
- MvxTextCountConverter
これらのクラスは全て「TextCount」の名前でコンバーターとして登録される。
コンバータークラスはCoreプロジェクトのものとプラットフォームごとのプロジェクトのものとで同じ名前空間で識別される。もし同一の名前のコンバータークラスが複数存在した場合には、最後に登録されたコンバータークラスが有効となる。
Touchプロジェクトの実装
Touchプロジェクトではレイアウトを編集し、コンバーターを使用したバインディングを定義する。
using Cirrious.MvvmCross.Binding.BindingContext;
using Cirrious.MvvmCross.Touch.Views;
using CoreGraphics;
using Foundation;
using ObjCRuntime;
using UIKit;
namespace CrossConverterSample.Touch.Views
{
[Register("FirstView")]
public class FirstView : MvxViewController
{
public override void ViewDidLoad()
{
View = new UIView { BackgroundColor = UIColor.White };
base.ViewDidLoad();
// ios7 layout
if (RespondsToSelector(new Selector("edgesForExtendedLayout")))
{
EdgesForExtendedLayout = UIRectEdge.None;
}
var label = new UILabel(new CGRect(10, 10, 300, 40));
Add(label);
var textField = new UITextField(new CGRect(10, 50, 300, 40));
Add(textField);
// 1
var labelCount = new UILabel(new CGRect(10, 90, 300, 40));
Add (labelCount);
var set = this.CreateBindingSet<FirstView, Core.ViewModels.FirstViewModel>();
set.Bind(label).To(vm => vm.Hello);
set.Bind(textField).To(vm => vm.Hello);
set.Bind(labelCount).To(vm => vm.Hello).WithConversion("TextCount"); // 2
set.Apply();
}
}
}
|
1で文字数を表示するラベルを画面へ追加する。
2でlabelCount
ラベルへ、Hello
プロパティをTextCount
コンバーターを使ってバインドする。WithConversion
メソッドでコンバーターを指定する。
Hello
プロパティが更新されたとき、CoreプロジェクトのMvxTextCountConverter
クラスが値を受け取り、その文字数をlabelCount
ラベルへ表示する。
Droidプロジェクトの実装
Droidプロジェクトも同様にレイアウトを編集し、コンバーターを使用してバインディングを定義する。[CrossConverterSample.Droid]プロジェクトの[Resources]-[layout]ディレクトリにある[FirstView.axml]ファイルを開き、その[Source]を次のように修正する。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
local:MvxBind="Text Hello" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
local:MvxBind="Text Hello" />
<!-- 1 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
local:MvxBind="Text TextCount(Hello)" />
</LinearLayout>
|
1で文字数を表示するTextView
を作成し、TextCount
コンバーターを設定してHello
プロパティをバインディングしている。Android Layout XML(.axml)ファイルではMvxBind
属性でTextCount(Hello)
のようにコンバーターとその引数となるプロパティを指定する。
アプリケーションの実行
ここでアプリケーションを実行すると、次のように表示される。テキストを編集すると、その文字数をカウントして画面へ表示する。
プラットフォームプロジェクトごとのコンバーターの実装
次に、プラットフォームごとに異なるコンバーターを実装してみよう。
今回はCoreプロジェクトを修正する必要はなく、TouchプロジェクトとDroidプロジェクトのみ追加で実装する。
Touchプロジェクトの実装
まず、Touchプロジェクト用のコンバータークラスを作成する。
CrossConverterSample.Touch
プロジェクトのルートディレクトリにConverters
ディレクトリを作成し、その下へMvxCountColorConverter.cs
ファイルを作成する(図4)。
MvxCountColorConverter
クラスは次のように実装する。
using System;
using Cirrious.CrossCore.Converters;
using UIKit;
namespace CrossConverterSample.Touch.Converters
{
public class MvxCountColorConverter : MvxValueConverter<string, UIColor>
{
protected override UIColor Convert (string value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int count = value.Length;
if (10 <= count) {
return UIColor.Red;
}
return UIColor.White;
}
}
}
|
クラス名の命名やMvxValueConverter
クラスの継承など、実装方法はTextCount
コンバーターとほとんど変わりはない。CountColor
コンバーターは与えられたプロパティの文字数が10以上のときUIColor.Red
を返し、10未満であればUIColor.White
を返す。
CountColor
コンバーターは変換後にUIColor
を返している。UIColor
クラスはTouchプロジェクトからのみ参照できるクラスであるため、MvxCountColorConverter
クラスをCore
プロジェクトのコンバーターとして実装することはできない。
次にTouchプロジェクトのバインディングを修正する。[CrossConverterSample.Touch]プロジェクト-[Views]ディレクトリ-[FirstView.cs]ファイルを次のように修正する。
using Cirrious.MvvmCross.Binding.BindingContext;
using Cirrious.MvvmCross.Touch.Views;
using CoreGraphics;
using Foundation;
using ObjCRuntime;
using UIKit;
namespace CrossConverterSample.Touch.Views
{
[Register("FirstView")]
public class FirstView : MvxViewController
{
public override void ViewDidLoad()
{
……省略(コントロールのレイアウトは先ほどと同じ)……
var set = this.CreateBindingSet<FirstView, Core.ViewModels.FirstViewModel>();
set.Bind(label).To(vm => vm.Hello);
set.Bind(textField).To(vm => vm.Hello);
set.Bind(labelCount).To(vm => vm.Hello).WithConversion("TextCount");
set.Bind(labelCount).For(v => v.BackgroundColor).To(vm => vm.Hello).WithConversion("CountColor"); // 1この一行を追加
set.Apply();
}
}
}
|
1の一行を追加する。labelCount
ラベルのBackgroundColor
プロパティへ、CountColor
コンバーターを使用してHello
プロパティをバインディングしている。
CountColor
コンバーターはHello
プロパティの文字数をカウントしてUIColor
を返すため、文字数によってlabelCout
ラベルの背景色が変化する。
Droidプロジェクトの実装
次に、Droidプロジェクトのコンバーターを作成しよう。
CrossConverterSample.Droid
プロジェクトのルートディレクトリへConverters
ディレクトリを作成し、その下へMvxCountColorConverter.cs
ファイルを作成する(図5)。
MvxCountColorConverter
クラスは次のように実装する。
using System;
using Cirrious.CrossCore.Converters;
using Android.Graphics;
using Android.Graphics.Drawables;
namespace CrossConverterSample.Droid.Converters
{
public class MvxCountColorConverter : MvxValueConverter<string, Drawable>
{
protected override Drawable Convert (string value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int count = value.Length;
if (10 <= count) {
return new ColorDrawable (Color.Red);
}
return new ColorDrawable (Color.Black);
}
}
}
|
実装方法もロジックも、TouchプロジェクトのMvxCountColorConverter
クラスと同様であるが、変換後の値としてColorDrawable
インスタンスを返す点が異なる。DroidプロジェクトでバインディングするViewのプロパティはTextView
クラスのBackgroud
プロパティであり、Background
プロパティはDrawable
型であるため、これにそろえた返り値となる。
DroidプロジェクトのCountColor
実装は、渡されたプロパティの文字数が10以上であればColor.Red
のColorDrawable
インスタンスを返し、10未満であればColor.Black
のColorDrawable
インスタンスを返す動作となる。
CountColor
コンバーターはTouchプロジェクトのコンバーターと名前が被っているが、TouchプロジェクトとDroidプロジェクトは依存関係がなく、どちらのプラットフォーム向けのビルドをした場合でも二つのコンバータークラスは衝突しない。
次に、Droidプロジェクトのバインディングを修正する。[CrossConverterSample.Droid]プロジェクトの[Resources]-[layout]ディレクトリにある[FirstView.axml]ファイルを次のように修正する。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
local:MvxBind="Text Hello" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
local:MvxBind="Text Hello" />
<!-- 1 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
local:MvxBind="Text TextCount(Hello);Background CountColor(Hello)" />
</LinearLayout>
|
1のMvxBind
属性の値を修正する。TextView
のBackground
プロパティへ、CountColor
コンバーターを使いHello
プロパティをバインディングしている。この修正により、入力された文字数によって文字数表示用のTextView
の背景色が変化する。
アプリの実行
これで全ての実装が完了した。アプリを実行すると以下のように表示される。テキストを編集すると文字数がカウントされ、文字数が10以上のとき、文字数表示の背景色が赤色となる。コンバーターをプラットフォームごとに実装したため、iOSアプリとAndroidアプリとでは10文字未満の場合に異なった背景色設定されている。
【コラム】MvvmCrossで提供されているコンバーター
MvvmCrossには、MvvmCrossプラグイン形式で複数のコンバーターが提供されている。いずれも使いやすいコンバーターであるため、確認しておこう。
MvxVisibilityValueConverter
クラスとMvxInvertedVisibilityValueConverter
クラスは、プロパティの値によってViewの表示/非表示の状態を切り替えるコンバーターである。以下の例に示すように、「Visibility」ターゲットを指定することで、iOSアプリではUView
クラスのHidden
プロパティを、AndroidアプリではView
クラスのVisibility
プロパティを設定する動作となる。
set.Bind(field)
.For("Visibility") // 「Visibility」ターゲットを指定
.To(vm => vm.VMProperty)
.WithConversion("Visibility");
|
MvxVisibilityValueConverter
クラスは以下の表に示すように柔軟に判定を行う。MvxInvertedVisibilityValueConverter
クラスは以下の条件を反転させたコンバーターである。
プロパティの型 | Visibleの条件 |
---|---|
string | nullではない、かつ、文字数が0文字ではない |
bool | trueである |
intまたはdouble | 0より大きい |
その他の型 | nullでないとき |
MvxVisibilityValueConverter
クラスとMvxInvertedVisibilityValueConverter
クラスは利用したいプラットフォームプロジェクトへ、NuGetリポジトリからMvvmCross Visibility Pluginをインストールすることで利用できる。
MvxNativeColorValueConverter
クラスはMvxColor
クラスをプラットフォームごとの色表現(iOSならUIColor
、Androidならint
値)へ変換するコンバーターである。MvxColor
クラスはCoreプロジェクトで共通にプラットフォームの色を扱うためのクラスとして用意されている。
MvxRGBAValueConverter
クラスはRGBの文字列による色表現を、それぞれのプラットフォームの色表現へ変換するコンバーターである。MvxRGBAValueConverter
クラスは次の文字列表現をパース可能である。
- “RGB”または“#RGB”形式
- “RRGGBB”または“#RRGGBB”形式
- “RRGGBBAA”または“#RRGGBBAA”形式
MvxRGBIntColorConverter
クラスはint
型による色表現を、それぞれのプラットフォームの色表現へ変換するコンバーターである。
MvxNativeColorValueConverter
クラス、MvxRGBAValueConverter
クラス、MvxRGBIntColorConverter
クラスは利用したいプラットフォームプロジェクトへ、NuGetリポジトリからMvvmCross Color Pluginをインストールすることで利用できる。※以下では、本稿の前後を合わせて5回分(第49回~第53回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
49. MvvmCrossでAndroidの画面の再生成に対応するには?
Androidアプリでは別アプリ移動時に画面が破棄され、アプリ再表示時に画面が復元される場合がある。この画面の再生成を、MvxViewModelのライフサイクルメソッドにより行う方法を説明する。
50. Xamarin.Formsでローカルデータベースを使用するには?
アプリを終了して再起動したときに、ユーザーデータを復活させたい場合、ローカルやクラウドにデータを保存することになる。その一つの方法として、SQLite.Netを使ってローカルDBに保存する方法を説明する。
51. 【現在、表示中】≫ MvvmCrossでカスタムコンバーターを作成するには?
MvvmCrossでのiOS/Androidアプリ開発において、バインディングする値を変換できるカスタムコンバーターの使い方を説明する。
52. Xamarin.FormsでTwitterクライアントを作成するには?
TwitterのAPIを扱えるライブラリであるCoreTweetを使用して、Twitterデータを検索するアプリを作成。CoreTweetの導入と、検索したテキストの表示までを紹介する。
53. MvvmCrossでWebBrowserプラグインを使用するには?
WebBrowserプラグインを追加・利用する例を通じて、MvvmCrossでのiOS/Androidアプリ開発におけるMvvmCrossプラグインの基本的な使い方を説明する。