インサイドXamarin(14)
Xamarin.Formsの基本構想と仕組み
クロスプラットフォームで使用できるモバイルUIライブラリであるXamarin.Formsの基本構想や仕組みについて説明する。
今回は、Xamarinが2014年5月に新しく公開した、クロスプラットフォームで使用できるUIライブラリであるXamarin.Formsについて説明する。
Xamarin.Formsの簡単な使い方については、すでに「Tips: iOS/Androidの画面レイアウトを共通化するには?(Xamarin.Forms)」という記事が公開されているので、そちらを参照されたい。本連載はライブラリの使い方をリファレンス的に説明したり、Tipsの紹介を主眼としたりするものではないので、主にXamarin.Formsの基本構想や仕組みについて説明する。
Xamarin.Formsの概要
Xamarin.Formsは、XamarinおよびWindows Phoneのプラットフォームを対象とする、クロスプラットフォームのモバイルUIライブラリである。Xamarin.Formsを使用すれば、同一のUIコードを、Android、Windows Phone、iOSのいずれの環境でも動作させることができる。
Xamarinのモバイルアプリケーション開発の基本路線は「プラットフォームごとに適切なユーザーインターフェースを設計する」というものだ。この基本思想はXamarin.Formsにおいても変わっていない。Xamarin.Formsは、次の3通りのシナリオで使用することができる。
- UIの全てをXamarin.Formsで記述する。UIは100%クロスプラットフォームで動作する
- UIを基本的にXamarin.Formsで記述し、必要に応じてプラットフォーム別のUIコードを継ぎ足す
- プラットフォーム別のUIを記述するが、Xamarin.Formsで作成した共通のUI部品を部分的にプラグインする
この設計思想は、デスクトップのMonoで言えばXwtの設計に類似している。XwtがWPF、MonoMac、Gtk#それぞれに合わせたバックエンドを実装しているように(※第7回を参照)、Xamarin.Formsも、Android、Windows Phone、iOSのそれぞれに合わせたバックエンドを実装している。
具体的には、Xamarin.Forms.Core.dllが、プラットフォーム非依存のAPIを定義・実装し、プラットフォーム固有のXamarin.Forms.Platform.*(=Android|WP8|iOS).dll(※WP=Windows Phone)が、それぞれのUIコントロールのレンダリングを実際に実装している(これについては後ほど詳しく説明する)。
これは、monoのクロスプラットフォームなWindows Formsの実装でも伝統的に通ってきた道とも言えるが、実装のアプローチは大きく異なる。monoのWindows.Formsは、Windowsの描画APIであるGDI+を使用するSystem.DrawingというAPIに基づいて、UIコントロールの描画をプラットフォーム共通のコードで実現しており、monoチームはWindows以外のプラットフォーム用にlibgdiplusという描画ライブラリを作成した。Xamarin.FormsやXwtは、コントロールの描画のような低レベルの処理をプラットフォーム側に一任している。
XwtがXAMLをサポートしてビューとロジックの分離を実現しているように、Xamarin.FormsもXAMLをサポートしている。そして、Xamarin.Formsにはデータバインディングの機構も用意されている。Xamarin.Formsには、いわゆるMVVMパターンを実現するための基盤がそれなりにあると言える。
XAMLのサポート、データバインディングの仕組み、MVVMパターンの基盤、といった特徴は、Xamarin.Formsが.NET開発者にとって親しみを感じられるフレームワークであろうとした結果だと言える。Xamarin.Formsはプラットフォーム間の違いを実装レベルで吸収できるように設計されたライブラリであって、単純にWPFと同様のAPIになっていることを期待するのは間違いだが、Androidのアクティビティ遷移やフラグメントといったUIフレームワークの概念をゼロから学習する、あるいはiOSのMVCパターンやデリゲート機構をゼロから学習するよりは、平易に感じられるのではないだろうか。
Xamarin.Formsの使用例は、xamarin-forms-samplesというGitHubリポジトリをチェックアウトして、ビルドして動かしてみるとよいだろう。
Xamarin.Forms本体はNuGetで配布されており、そのアップデートは、NuGetサーバー上で随時行われる予定だ。
Xamarin.Formsプロジェクトの作成
Xamarin Studioを起動して、新規プロジェクト作成ダイアログを開くと、そこには、Xamarin 3以前のバージョンにはなかった「Mobile Apps」というプロジェクトカテゴリが、新しく追加されている。その実体はXamarin.Formsのプロジェクトであり、下記の3種類がある。
- PCLプロジェクト: Xamarin.FormsのPCLを参照する、新しいPCLのアプリケーション用プロジェクトと、それをAndroidおよびiOSで起動するだけのアプリケーションプロジェクトが作成される。
- 共有プロジェクト: 共有プロジェクトは、Xamarin 3でMonoDevelopに新しく追加された、ソースの集合体を定義するだけのプロジェクトモデルで、もともとはVisual Studio 2013 Update 2で追加された。このモデルでは、「共有プロジェクト」モデルに基づくライブラリプロジェクトと、それをAndroidおよびiOSで起動するだけのアプリケーションプロジェクトが作成される。
- PCLライブラリ: Xamarin.FormsのPCLを参照する、新しいPCLのライブラリを作成する。
共有プロジェクトのアプリケーションは、PCLとは互換性が無く、PCLを参照することはできない。共有プロジェクトの特殊ぶりは、プロジェクトにソースファイル以外の項目が無い(「参照」すら無い)ことからもうかがい知れる。
Xamarin.Formsの新規ソリューションで作成されるプロジェクトの構成は、PCLの場合なら次のようになる(既存のソリューションに新規Xamarin.Formsアプリケーションを追加しても、プラットフォーム別のプロジェクトは新規作成されない。これはやや特殊な挙動になっている)。
新規作成されるプロジェクトは3つある。
- PCLのXamarin.Formsアプリケーション
- 上記アプリケーションを参照し、Androidのフレームワーク上で実行するAndroidアプリケーション
- 上記アプリケーションを参照し、iOSのフレームワーク上で実行するiOSアプリケーション
- *1 Visual Studio上で作成すると、さらにWindows Phone 8のプロジェクトも作成されるが、今回はXamarin Studioで行ったため、割愛する。
Xamarin.Formsアプリケーションは、Xamarin.FormsのパッケージをNuGet経由で参照する。NuGetサポートは、Xamarin.Formsのリリースと同時にリリースされたXamarin Studio 5.0で、公式に追加された(NuGetコミュニティアドインを作成していたSharpDevelopチームのハッカーがXamarinに参加している)。アプリケーションを新規作成すると、まず、このNuGetパッケージをダウンロードし展開する処理がバックグラウンドで実行される。
Xamarin.Formsアプリケーションプロジェクトは、名前こそ「アプリケーション」ではあるが、(PCLでも共有プロジェクトでも)プロジェクトの種類としてはライブラリであって、実際に各プラットフォーム上で動作するアプリケーションではない。同時に作成された、AndroidおよびiOS(Visual StudioであればさらにWP)の各アプリケーションが、それぞれのプラットフォーム上で動作するアプリケーションである。アプリケーションを「実行」したり「デバッグ」したりする場合は、これらのプロジェクトをスタートアッププロジェクトに設定するか、コンテキストメニューから実行するとよい。
- *2 補足となるが、地図コントロールをサポートするXamarin.Forms.Mapsを使いたい場合は、先のスクリーンショットのように、Xamarin.Formsアプリケーションだけでなく、各プラットフォームにもパッケージを追加する必要がある。そうしないと必要な初期化処理を行うためのクラスが参照できない。
アプリケーションコードのテンプレートは、極めて単純である。ここではAndroidの場合を取り上げるが、Activity.OnCreate
メソッドをオーバーライドする、Androidの典型的なアプリケーションのコードだ。
public class MainActivity : AndroidActivity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
Xamarin.Forms.Forms.Init (this, bundle);
SetPage (App.GetMainPage ());
}
}
|
iOSでもUIApplicationDelegate.FinishedLaunching
メソッドをオーバーライドするだけの、同程度に単純なコードだ。
public partial class AppDelegate : UIApplicationDelegate
{
UIWindow window;
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
Forms.Init ();
window = new UIWindow (UIScreen.MainScreen.Bounds);
window.RootViewController = App.GetMainPage ().CreateViewController ();
window.MakeKeyAndVisible ();
return true;
}
}
|
その中では、Xamarin.Formsを初期化した後、App.csファイルに定義された、Xamarin.FormsのUIオブジェクトを返すstaticなGetMainPage()
を呼び出すだけだ。Xamarin.Formsの流儀に従うなら、Xamarin.Formsのコントロールの初期化などを除いては、このMainActivityのコードをいじる機会はあまりないはずだ。
デフォルトのApp.GetMainPageメソッドの実装は、次のような例となっている。
public class App
{
public static Page GetMainPage ()
{
return new ContentPage {
Content = new Label {
Text = "Hello, Forms !",
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.CenterAndExpand,
},
}.;
}
}
|
まだXamarin.FormsのAPIについて解説していないが、上記のコードは、直感的で分かりやすい(と筆者は信じる)ので、ここではこのコードの内容を文章で説明することはしない。
先のAndroidActivityは、通常のAndroidフレームワークにおけるActivityであって、Xamarin.FormsでないActivityと併用できる。これは「基本的にネイティブアプリケーションだが、部分的にXamarin.Formsを使用する」アプローチの、1つの例と言える。
アプリケーションのビルド・実行・デバッグについては、通常のiOS/Android/WPアプリと同様であり、Xamarin.Formsで特筆すべき事柄はない(本連載の以前の記事を参照いただきたい)。
Xamarin.FormsアプリケーションのAPIとコード
ここからはXamarin.Formsアプリケーションのコードを書くために使用するAPIについて説明していこう。
まず、Xamarin.Formsの基本的なコンセプトを3つ挙げる。
- View: Xamarin.Formsのコントロールの基底クラス。
- Layout: UIの階層構造を保持し配置方法を指定できるView。
- Page: アプリケーション上でスクリーンまたは個別のページビューを作成できるView。
「ページ」はWPFにおけるPageのような存在である。ページの例としては、以下のようなものがある。
- NavigationPage: iOSのUINavigationControllerに近いコンセプトのページ。複数ページ間を遷移でき、ナビゲーションバーの[Back]ボタンで以前のページに戻れる。
- ContentPage: 通常のViewとPageの中間にあるような存在。Contentプロパティに任意のViewを指定できる。
- TabbedPage: iOSのUITabBarControllerに近いコンセプトのページ。複数のページをタブ切り替えできる。
- MasterDetailPage: iOSのUISplitViewControllerに近いコンセプトのページ。MasterビューとDetailビューを、環境(スクリーンサイズなど)に合わせて適宜表示する。
- CarouselPage: カルーセルを用いて、スワイプによって複数の子ページをナビゲートできるページ。
レイアウトについては、次の3種類がある。
- StackLayout: これは単純に縦あるいは横に、UI要素を並べていく方式のレイアウトである。
- RelativeLayout: これは、UI要素を相互に相対的に配置していく、複雑な方式のレイアウトである。
- AbsoluteLayout: 配置座標を絶対値で固定するレイアウトである。
- GridLayout: 複数のUI要素を、行・列に基づいて配置していく方式のレイアウトである(クラス名はGridである)。
レイアウトはあくまで配置を調整する概念的な存在であり、実体は単なるViewである。内容を配置するためのものとしては、ContentView、ScrollView、Frameもこの範ちゅうに含まれると言える。
ビューは、おおむね「コントロール」を表していると理解してもいいだろう。Label、Button、Entry、Editor、Switch、TimePicker、ToolBar、SearchBar、WebViewなど、さまざまなコントロールが存在する。また、Xamarin.Forms.Maps.dllには、各プラットフォームのMapコントロールが存在する。
これらはXamarin.FormsサンプルのFormsGalleryというアプリケーションで、ざっと見ることができる。
また、公式サイトのドキュメントでは、Xamarin.Formsのアプリケーションを作成する上で役に立つであろう、さまざまなAPIの使い方について、個別にまとめられている。「Working with Xamarin.Forms」というセクションには、(2014年6月の時点で)画像、フォント、色、ポップアップ、ファイル(WPのみSystem.IOが使えないので、代替案がいろいろ例示されている)、マップ、ローカルデータベース(SQLite.Netを使う)、プラットフォームの違いの吸収(Device
クラス)、といったトピックが挙がっている。
データバインディングとXAMLサポート
Xamarin.FormsにおけるロジックとUIの分離は、いわゆるMVVMパターンを想定している。Xamarin.Formsにはデータバインディングの機構をサポートする、BindableObject
とBindableProperty
という仕組みがある。これはWPFやSilverlightにおけるDependencyObject
やDependencyProperty
に相当する(Monoチームが過去にMoonlightとしてこれらを実装していたことを思い出していただきたい)。このBindableObject
はINotifyPropertyChanged
を実装している(単にイベントを呼び出しているのみである)。
Xamarin.Formsでは、一方向および双方向のデータバインディングが可能だ。単純な一方向(OneWay
)、双方向(TwoWay
)の他にも選択肢があるので、詳しくはBindingMode列挙体を参照されたい。
また、Xamarin.Formsは「XAMLをサポート」している。この表現は独り歩きしがちだが、その具体的に意味するところは、Xamarin.FormsのUIコントロールツリーをXAMLで記述して実行時にインスタンス化できる、ということである。XAMLをシリアライゼーション・フォーマットとして使用したからといって、データバインディングが自動的にサポートされたり、GUIフレームワークが魔法のようにWPFと同様になったりするようなことはない。
System.Xaml.dll
を使用しているXwtとは異なり、Xamarin.Formsでは、SilverlightのLinux用実装であったMoonlight(第4回参照)の開発で使用されていたXAML実装を基にしている(ただし、Xamarin.FormsではXamlReader
はAPIとして公開されていないし、そもそも使用されていない)。これは、もともとMoonlightの開発を担当していたハッカーが担当していること、System.Xaml.dll
がモバイル向けライブラリとして開発されていないことが、理由として大きい(monoのSystem.Xaml.dll
はXamarin.AndroidやXamarin.iOSでビルドできるが、WP8ではビルドできない)。
Xamarin.FormsのGUIデザイナーは存在せず、XMLをエディターで手書きしなければならない。プロジェクトからXAMLファイルを新規作成することは可能で、あとはXamarin StudioのXMLエディターであれば、Xamarin.FormsのXAMLスキーマに基づいて、<キーやCtrl+Spaceキーで補完候補を表示できるので(次の画面を参照)、それを頼りにUI要素を記述することになるだろう。
Visual Studioで設計できるのは「XAML」ではなく「WPF」であって、これを再利用することはできない。Xamarin Studioも、プロパティグリッドやUIコンポーネントのリストやドキュメントビューといった、GUIデザイナーの基盤を有しているに過ぎず、Xamarin.Formsをサポートしているわけではない(筆者としては、何がこのUIフレームワークのデザイナーの最適解たりうるのか、答えが定まらない)。
データバインディングのコードについて詳説すると、テキストが大きくなりすぎるので、サンプルの紹介に留めさせていただきたい。データバインディングの方法は、XAMLによる方法とコードによる方法があるが、XAMLの場合はTipCalcサンプル、コードの場合はMobileCRMサンプルなどが参考になるだろう。
最後に、Xamarin.Formsの公開から1カ月ほど経って、XAMLを使うためのガイドとなるドキュメントが新しく公開されたので、こちらも参考にされたい。
レンダリングの詳細とカスタムレンダリングの実装
Xamarin.Forms.Core.dll
に含まれるXamarin.FormsのUIコントロールは、単にPCL(Profile 78)であるというだけでなく、特定のプラットフォームに依存するコードが含まれていない。UIコントロールのAPIを定義しただけの、概念的なライブラリである。プラットフォームごとにネイティブUIのAPIを使用して実装されたコードは、Xamarin.Forms.Platform.*(=Android|WP8|iOS).
dllに含まれている。
UIコントロールの大半には、それらを描画する「レンダラー」と呼ばれるクラスが存在する。例えば、Xamarin.Forms.Core.dll
に含まれる、テキストボックスを表すEntry
クラスに対しては、Xamarin.Forms.Platform.*.dll
にEnterRenderer
クラスが存在し、テキストボックスのレンダリングを実装している。これらのレンダラークラスは、それぞれのアセンブリの中で定義されているViewRenderer
の派生クラスであり、ViewRenderer
は、AndroidであればView
クラス、iOSであればUIView
クラス、WPであればCanvas
クラスから、それぞれ派生している。
- *3
Xamarin.Forms.Maps.dll
のレンダラーは、Xamarin.Forms.Maps.*(=Android|WP8|iOS).dll
に含まれている。
Xamarin.FormsのUIコントロールは、独自のコードによって独自のレンダリングを実装することもできる。ただし、Xamarin.Formsは、プラットフォーム別のAPIを使用して実装されているものであり、Xamarin.Forms自身のAPIは、プラットフォームに依存しない、最大公約数的なコードしか記述できない。そのAPIに基づいてユーザーコントロールを作成できる、というだけでは、各プラットフォームの機能を十分に活用できているとは言えない。カスタムレンダリングは、プラットフォームに依存したコードによって実現できるようにあるべきだ。
従って、(高度な)カスタムレンダリングを実装する手順は、ASP.NETやWPFなどで単純にユーザーコントロールを作成する方法よりは、少々複雑なことになる。具体的には、以下のようになる。
- UIコントロールクラスを定義する
- UIコントロールに対応する独自のレンダラークラスを実装する
- アセンブリ属性として
Xamarin.Forms.ExportRendererAttribute
を使用して、対象となるコントロールの型と、レンダラーの型の組み合わせを定義する(Xamarin.iOSには、さらにMonoTouch.UIKit.UIUserInterfaceIdiom
を指定するコンストラクターも存在する)
最後の手順に出てくるExportRendererAttribute
は、Xamarin.Forms.Platform.*.dll
で定義されている。カスタムレンダリングを定義する際には、プラットフォーム別の実装を提供することが想定されている。プラットフォームに依存しないレンダリングを定義できるなら、ExportRendererの機構を使う必要はないだろう。
最後に注意点だが、XAML上でカスタムレンダラーを用いるコントロールを記述するユースケースを考えると、カスタムレンダラーを利用するコントロールは、PCLで実装した方が無難である*4。XAMLでカスタムコントロールの使用を記述する時は、その型のアセンブリ名を記述することになるが、共有プロジェクトのモデルを使用した場合、アセンブリはあくまで各プラットフォームについて生成されるので、XAMLで記述してクロスプラットフォームのUIを作成することが、事実上不可能になってしまう。
- *4 カスタムコントロールをPCLで定義するという意味であって、プラットフォーム固有のレンダラーをPCLで実装するという話ではない。
その他のプラットフォーム中立コード
Xamarin.Formsには、プラットフォーム別の機能を呼び出す簡単な機構として、DependencyService
という仕組みが用意されている。使い方は簡単だ。
- プラットフォーム依存の機能について、インターフェース
I
を定義する。このインターフェースはXamarin.Formsアプリケーションの中で定義すればよい - Xamarin.Formsアプリケーションでは、
DependencyService.Get<I>
メソッドを使用して、インターフェースI
のインスタンスを取得する - 各プラットフォーム(Android/WP/iOS)のアプリケーション側で、そのインターフェース
I
を実装した型を実装し、その型をDependencyAttribute
でアセンブリ属性として登録する
また、第1回でも言及したが、Xamarinでは、以前から、プラットフォーム中立のモバイル機能の共通APIとして、Xamarin.Mobileというライブラリを提供している。これは、以下の3種類の機能について、各プラットフォームAPIの上に共通APIを構築したものだ。
- Xamarin.Contacts: アドレス帳API
- Xamarin.Geolocation: ジオロケーションAPI
- Xamarin.Media: メディアアクセスAPI(カメラ、ビデオ、メディアファイルアクセスなど)
Xamarin.MobileはソースコードもGitHubで公開されているので、必要であれば自ら手を加えることもできる。Xamarin.Mobileは2014年6月現在、Xamarin.FormsのPCLプロジェクトに追加できるNuGetパッケージを提供していないので、このライブラリはプラットフォーム別のアプリケーションでのみ使用できる。前述のDependencyServiceの仕組みを利用すれば(各種インターフェースを定義する必要が生じるが)、Xamarin.Formsアプリケーションのプロジェクトからでもある程度は利用できることだろう。
最後に
本連載は、モバイル用ライブラリを紹介した今回をもってひと区切りということになる。これまでお付き合いいただいた読者の皆さまには感謝したい。
XamarinやMonoプロジェクトは、.NETとC#の有用性を、非マイクロソフトプラットフォームを中心に展開してきた、特異点のような存在であり、筆者はこれらに、Windows系開発者、UNIX系開発者、iOS/Androidアプリケーション開発者、そしてJavaやLL系言語の開発者など、さまざまな立ち位置から関わってきた人たちを見てきた。これほど魅力的なプラットフォームやフレームワークには、なかなか出会えないことだろう。
Xamarin Test CloudやXamarin.Macなど、今後論じられる価値のあるトピックもまだまだあり、今後さまざまな人に、Xamarin製品について論じてもらえることを期待したい。
※以下では、本稿の前後を合わせて5回分(第10回~第14回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
10. Xamarin.AndroidにおけるJava相互運用の仕組みと、Javaバインディング・プロジェクト
Xamarin.AndroidでJavaとの相互運用を実現するアーキテクチャについて、さらにメモリ管理などの注意点を説明。さらにXamarin.Androidの制限事項についても解説する。
11. Xamarin Studio/MonoDevelopの基本機能と、C#コーディング補助機能
MonoDevelopとXamarin Studioはどう違うのか? MonoDevelopの基本的な機能を解説。C#コーディング補助機能についても紹介する。
12. MonoDevelopにおけるビルド/実行/デバッグと、iOS/Android向けのGUIデザイナー
MonoDevelopでアプリをビルド/実行/デバッグするための機能を解説。iOS/Android向けのGUIデザイナーや、MonoDevelopのカスタムアドインについても紹介する。
13. Xamarinと、ポータブル・クラス・ライブラリ(PCL)
複数プラットフォーム向けのライブラリを作れるPCLの概要と利点について解説。また、Xamarin.iOSやXamarin.Androidでの利用方法や、XamarinでPCLを実現する仕組み、PCLの課題を説明する。