de:code の直前に発表になった Xamarin.Forms ですが、拙著のサンプルも、それぞれビューを作っているわけで。
日経BP書店|C#によるiOS、Android、Windowsアプリケーション開発入門
http://ec.nikkeibp.co.jp/item/books/P98340.html
そんな訳で、サンプルコードの TMPuzzle を Xamarin,Forms にコンバートしています。サンプル自体は一枚絵(ひとつのビュー)になっているので、比較的コンバートは楽なハズ…なのですが、いくつかコツが要りそうなでメモ的に残しておきます。
まだ途中の段階ですが、iOS/Android版を Github に公開しておきます。
https://github.com/moonmile/TMPuzzleXForms
■ビューを XAML で作る
https://github.com/moonmile/TMPuzzleXForms/blob/master/TMPuzzleXForms/TMPuzzleXForms/MainPage.xaml
サンプルでは、axml/storyboard/XAML で作ったので、今回も XAML で作ります。Xamarin Studio ではデザイナは動かないのですがコード補完ができるのでだいたいの動きが想像できます。デモのサンプル https://github.com/xamarin/xamarin-forms-samples にある ButtonXaml が XAML を使ったコードです。*.xaml と *.xaml.cs の2つがあるので、コードビハイドが書けます。MVVM スタイルにするときはバインドを使えばいいのですが、ここではがりがりとコードを書きつけてしまいます。バインド自体は、MS謹製のXAMLと同じように Text=”{Binding …}” の構文で書けるので、ここは便利。使えるコントロールは http://iosapi.xamarin.com/?link=N%3aXamarin.Forms を参照すれば OK です。TextBox が Editor になっているとか、いくつか違いがありますが、このあたりは iOS/Android/Windows の混合になるので仕方がないところです。
ビュー自体は、レイアウトが異なるので、http://developer.xamarin.com/guides/cross-platform/xamarin-forms/controls/layouts/ を見てベースを決めます。TMPuzzle は Windows ストア寄りに作ってあるので Grid を使ってみたのですが、iOS だとうまくレイアウトができませんね。スマートフォンのような小さ目の画面であれば? Frame を使って iOS のように位置固定(Windows の Xaml ならば canvas)で良いかもしれません。TMPuzzle 自体はゲームアプリなので、たくさんのコントロールを乗せていますが、普通のコンテンツを表示するならば、ContentView や StackLayout でもいいかもしれません。
プロジェクトの構成は、Blank App (Xamarin.Forms. Shared) を使っています。PCL でも良いのですが、サンプル自体はコードビハイドを使っているのと、プラットフォームごとの処理が必要になって時には共有プロジェクト内で #if してしまえばよいので、こっちのほうが融通が利きます。
https://github.com/moonmile/TMPuzzleXForms/blob/master/TMPuzzleXForms/TMPuzzleXForms/App.cs 共有プロジェクトの App.cs を書き換えます。MainPage.xaml を共有プロジェクトに追加したので、new MainPage() でページを作成します。コードビハイドの場合は、このあとゴリゴリ内容を書いていますが、ひとまず XAML を書いて Andorid か iOS のシミュレーターで動かすとよいでしょう。
1 2 3 4 5 6 7 | public class App { public static Page GetMainPage() { return new MainPage(); } } |
画像リソースも多分、共有プロジェクトにおけるハズなのですが、フォルダが異なる(Android の場合は Resources/Drawable、iOS の場合は Resources)となるので、各プラットフォームのフォルダに置いています。画像リソースは一律、
1 | var img = ImageSource.FromFile( "MarkNone.png" ); |
のように取り出せるので、iOS/Android を区別する必要はありません。
ちなみに、Xamarin.Forms とは関係ないですが、ファイル呼び出しは
1 | var file = System.IO.Path.Combine(documents, "mydata.xml" ); |
のように共通化できます(Windows Phone の場合は調査中)。
■MainPage.xaml.cs
ページを表示するときの初期化は、Android では OnCreate、iOS では ViewDidLoad になるのですが、Xamarin.Forms では(多分)OnAppearing です。 ヘルプを見ると When overridden, allows application developers to customize behavior immediately prior to the Xamarin.Forms.Page becoming visible. とあるので、次回表示するときも呼び出されてしまうような気がするのですが、ここは要調査ですね。TMPuzzle は1枚絵なのでこれを使っています。
各プラットフォームで呼び出す MainActiveity や AppDelegate はそのままです。もともと共通化を意識して作ったサンプルプログラムなので、Xamarin.Forms に移植するとプラットフォーム毎の差がほとんどなくなって、画像ファイルの切り替えぐらいになります。
■画像のクリックイベントはTapGestureRecognizerを使う
Button コントロールには Click イベントがあるのですが、ImageやLabelなどには Click イベントがありません…というか、タップイベント全般がありません。ざっと調べてみると、コントロールに対するユーザーイベントは Click と TextChanged ぐらいしかありません。
うーむ、困った。これで万事休すかな、と思ったのですが TapGestureRecognizer ってのがありました。iOS と同じでコントロールに対するイベントを取ってきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 | TapGestureRecognizer tapGestureRecognizer = new TapGestureRecognizer { TappedCallback = img_Click }; // 表示用マークにイベントをつける _marks = new Image[DataModel.BOARD_X_MAX * DataModel.BOARD_Y_MAX]; _tags = new Dictionary<Element, int >(); for ( int i = 0; i < DataModel.BOARD_X_MAX * DataModel.BOARD_Y_MAX; i++) { var img = _marks[i] = this .FindByName<Image>( string .Format( "mark{0}" , i)); img.GestureRecognizers.Add(tapGestureRecognizer); _tags[img] = i; } |
こんな風に、イベント先の img_Click を設定しておいて、img.GestureRecognizers.Add のように追加していきます。GestureRecognizers 自体は、View クラスにあるので大抵のコントロールにはついています。
いわゆる sender が View オブジェクトで渡されるので、これを目的のコントロールにキャストします。引数自体は object 型なのですが、中身は不明。Image をクリックすると null が来ています…が、ひとまず画像やラベルのタップイベントを取得できます。
1 2 3 4 5 6 | public async void img_Click(View view, object args) { // 再入禁止 if (_flag == true ) return ; _flag = true ; Image mark = view as Image; |
■Android と iPhone の動作
同じ XAML ファイルを使ったのですが、iOS のほうがレイアウトが崩れています。というか、Grid だとちょっと辛いものがありそうですね。Xamarin.Forms のサンプルは、flowlayout を使っているので、どれも同じようになりますが、複雑なレイアウトの場合にはそれぞれの調節が必要っぽいです。
引き続き Windows Phone を作成。