Xamarin逆引きTips
Xamarin.Androidで画面遷移を行うには?
Xamarin.Androidで画面を追加する方法と、2つの画面間を遷移し、遷移先にデータを渡す方法、遷移先から返り値を得る方法を解説する。
Xamarin.Androidで画面遷移を行う方法を解説する。また、次の画面にデータを渡す方法、遷移した画面から結果を返してもらう方法についても述べる。
画面の新規作成
「Tips:Xamarin.Androidで画面をレイアウトするには?」では、新しい画面の作成には触れなかったので、ここで解説しておこう。なお、本Tipsで使うプロジェクトは「Android Ice Cream Sandwich Application」(プロジェクト名:「Tips004」)で作成したものを使用する。
1Activityクラスを追加する
[ファイル]メニュー から、[新規]-[ファイル]と選択して[新しいファイル]ダイアログを表示し、その中から[Android]-[Android Activity]を選択する。そしてクラス名をここではSecondActivity
と入力して[新規]ボタンを押す。
なお、以前解説した通り、AndroidManifest.xml
ファイルへの記述は必要ない。
2レイアウト定義ファイルを追加する
続いて、レイアウト定義の.axml
ファイルを追加する。
再度、[新しいファイル]ダイアログを開き、その中から[Android]-[Android Layout]を選択する。レイアウトファイル名はActivitySecond
として[新規]ボタンを押す。
3Activityクラスとレイアウト定義ファイルを関連付ける
ここまでで、プロジェクトにSecondActivity.cs
ファイルとActivitySecond.axml
ファイルが追加されているはずだ。だが、両者はまだ関連付いていないので、SecondActivityクラスにActivitySecond.axmlファイルを読み込むコードを追加する必要がある(ネイティブのEclipse+ADTでは、この手順を一括で行ってくれるので、Xamarin Studioはそれと比べると機能的に見劣りする)。
SecondActivity.cs
ファイルのOnCreate
メソッドに、以下のように1行追加する。
[Activity(Label = "SecondActivity")]
public class SecondActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.ActivitySecond); // <--追加
}
}
|
単純な画面遷移
Xamarin.Androidでも、ネイティブ同様、「インテント」を利用して画面遷移を行う。
MainActivity
のボタンを押すと、SecondActivity
に遷移するコードは以下のようになる。
[Activity(Label = "Tips004", MainLauncher = true)]
public class MainActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.ActivityMain);
var button = FindViewById<Button>(Resource.Id.myButton);
button.Click += (_, __) =>
{
var intent = new Intent(this, typeof(SecondActivity));
StartActivity(intent);
};
}
}
|
ネイティブの開発者にはおなじみのコードだろう。遷移先Activityの型の指定が、Java言語ではSecondActivity.class
とするのに対して、C#ではtypeof(SecondActivity)
とするのが異なるだけだ。
遷移先の画面にデータを渡す方法
次に、遷移元の画面からデータを渡しながら遷移する方法を解説する。
1必要なアセンブリの参照を追加する
アセンブリとは、.NETの世界の「ライブラリ」のことだ、Javaの.jar
のようなものだ。Xamarin.Androidでは、画面間でデータを渡すのに必要な機能を別なアセンブリに分けており、それは既定のプロジェクトには含まれていないので追加する必要がある。
手順は、メニューバーから[プロジェクト]-[参照アセンブリの編集]と選択して[Edit References]ダイアログを表示し、[Packages]タブの中から[Mono.Android.Export]にチェックを入れ[OK]ボタンを押す。
2データクラスを作成する
さて、では画面間でやりとりされるデータを表すクラスを作成する。
Androidでは、このような画面と画面(あるいは画面とサービスなど)の行き来は、同じプロセス内で行われるとは限らないため、その間で扱うデータはParcel
(=英単語の意味は「小包」で、つまり小さなデータなどを包むためのメッセージング用のコンテナー)に保存できなければならない。「Parcelに保存できる」データであることを示すインターフェースがParcelabel
だ。
画面から画面へ受け渡すデータクラスは、このParcelableを実装する必要がある。まずはその方法を見てみよう。
Parcelableの実装方法(Java言語でのネイティブ実装の例)
データクラスとして、Name
(名前)とAge
(年齢)を持つCard
(カード)クラスを作成する。なお、このパートは、ネイティブとXamarin.Androidとで実装方法がやや異なるので、両者を比較しながら解説する。
Parcelableインターフェースを実装したCardクラスは、ネイティブ(Java言語)では下のようになる。
1
2
|
public class Card implements Parcelable {
private final String _name;
private final int _age;
public String getName() {
return _name;
}
public int getAge() {
return _age;
}
public Card(String name, int age) {
_name = name;
_age = age;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(_name);
dest.writeInt(_age);
}
public static final Parcelable.Creator<Card> CREATOR = new Parcelable.Creator<Card>() {
public Card createFromParcel(Parcel in) {
String name = in.readString();
int age = in.readInt();
return new Card(name, age);
}
public Card[]newArray(int size) {
return new Card[size];
}
};
}
|
・このクラスのデータをParcelに書き込むために呼び出されるのが1のメソッドである。Parcel.writeString(_name)
とParcel.writeInt(_age)
が実際の書き込み処理だ。
・Parcelからの読み出しは、静的なCREATOR
フィールドが持つParcelable.Creator
オブジェクトによって行われる。2がそれで、実際の読み出し処理はcreateFromParcel
メソッド内で行っている。
このように、ネイティブでは、Parcelへの書き込みはwriteTpParcel
メソッドで、読み出しはCREATOR
フィールドから得られるParcelable.Creator
オブジェクトによって行われる。
Parcelableの実装方法(Xamarin.Androidによる実装の例)
では、Xamarin.Androidでは、どのように実装するかを見てみよう(「Card」という名前でクラス用の.csファイルを追加して、下記のコードを記述する)。
1
2
3
|
using Android.OS;
using Java.Interop;
……省略(namespace部分)……
public class Card : Java.Lang.Object, IParcelable
{
public string Name { get; private set; }
public int Age { get; private set; }
public Card(string name, int age)
{
this.Name = name;
this.Age = age;
}
public int DescribeContents()
{
return 0;
}
public void WriteToParcel(Parcel dest, ParcelableWriteFlags flags)
{
dest.WriteString(this.Name);
dest.WriteInt(this.Age);
}
// public static final Parcelable.Creator の代わり
[ExportField("CREATOR")]
public static IParcelableCreator GetCreator()
{
return new CardParcelableCreator();
}
class CardParcelableCreator : Java.Lang.Object, IParcelableCreator
{
Java.Lang.Object IParcelableCreator.CreateFromParcel(Parcel source)
{
var name = source.ReadString();
var age = source.ReadInt();
return new Card(name, age);
}
Java.Lang.Object[] IParcelableCreator.NewArray(int size)
{
return new Java.Lang.Object[size];
}
}
}
……省略……
|
・1は、JavaではParcelable
だったインターフェースが、.NETの命名規則に従ったIParcelable
になっているのはささいなことだが、Java.Lang.Object
クラスから派生させている点に注目したい。画面遷移はXamarin.AndroidでなくAndroid OSで行われる処理であり、そこで扱われるデータにはJavaオブジェクトが必要だからだ。
・2がCard.java
ファイルの実装例と大きく異なる点だ。ネイティブでは、静的なCREATOR
フィールドを利用するが、Xamarin.AndroidではこのようにExportField
属性を付与した静的メソッドでIPercelableCreator
オブジェクトを返却することで実現する。
・C#言語では、Java言語のように匿名クラスを使用できないので、別途、内部クラスとしてCardParcelableCreator
クラス(=3)を用意する。注意すべきは、CreateFromParcel
メソッドの戻り値がJava.Lang.Object
であり、ジェネリクス(Generics/ジェネリック)による型強制がないので、返却するオブジェクトを間違えないようにすることだ。
このように、Xamarin.Androidで画面間で扱われるデータクラスの実装方法は、ネイティブと少し異なるので注意が必要だ。また、このような名前と年齢しか持たないデータクラスは、プリミティブな(=基本データ型しか使用していない単純な)クラスでありたいものだが、Androidの画面遷移においては、OSの仕組みに強く依存しているので、上記のコード例のようにAndroid.OS
/Java.Interop
名前空間への参照が必要だ。そのため、このCardクラスは(Xamarin.Androidではない)Xamarin.iOSでは使用できない。クロスプラットフォーム開発をする際に考慮すべき点となるので、覚えておいてほしい。
3画面遷移時にデータを渡す
データクラスが実装できれば、あとはそのインスタンスを画面遷移時に渡すだけだ。以下のようなコードとなる。
1
|
public class MainActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.ActivityMain);
var button = FindViewById<Button>(Resource.Id.myButton);
button.Click += (_, __) =>
{
var card = new Card("Yamada", 25);
var intent = new Intent(this, typeof(SecondActivity));
intent.PutExtra("data", card);
StartActivity(intent);
};
}
}
|
・1で、CardのデータをIntentに「詰め」ている。
Intent.PutExtra
メソッドは、さまざまなオーバーロードがある。基本データ型(intやStringなど)だけの場合は、intent.PutExtra("country", "Japan")
などと、データクラスを用意しなくても詰められることは、Android開発者にはあらためて説明する必要もないだろう。
4遷移先の画面でデータを受け取る
遷移先であるSecondActivity
で、元の画面から渡されたCardを受け取る。その前に受け取ったデータを表示するためのレイアウトを用意しておこう。
[ソリューション]パッドの/Resources/Layout/ActivitySecond.axml
をUIデザイナーで開いて、図4のように、名前と年齢を表示するEditText
ウィジェット(=Toolbox上の[Plain Text]と[Number])、それからButton
ウィジットを配置する。ボタンはこのあと解説するが「遷移元の画面に結果を戻す」のに使うものだ。それぞれのid
プロパティは図4にならって設定してほしい。
そして、SecondActivity.cs
ファイルのOnCreate
メソッドでデータを受け取る。以下のようなコードだ。
1
2
|
[Activity(Label = "SecondActivity")]
public class SecondActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.ActivitySecond);
var textName = FindViewById<EditText>(Resource.Id.textName);
var textAge = FindViewById<EditText>(Resource.Id.textAge);
var intent = this.Intent;
if (intent != null && intent.HasExtra("data"))
{
var card = intent.GetParcelableExtra("data") as Card;
textName.Text = card.Name;
textAge.Text = card.Age.ToString();
}
}
}
|
・1で、Intent
プロパティに、データが渡されているかをチェックする。Activity.Intent
プロパティはネイティブではActivity.getIntent()
メソッドに相当するものだ。Xamarin.Androidではこのようにgetter/setterメソッドは、プロパティに置き換えられている。
・2で、渡されたデータを取得している。as
句は、その後ろに指定する型に変換できなければnull
を返すC#の言語機能だ。
このプログラムを実行すると、下図のような結果になる。
遷移先の画面からデータを返す方法
最後に、遷移したSecondActivity
から、遷移元のMainActivity
にデータを返す方法を解説する。
Androidでは、遷移元の画面が、遷移先からの返り値を得るには、StartActivity
メソッドではなくStartActivityForResult
メソッドを使う必要がある。まずは、MainActivity.cs
ファイルを以下のように修正する。
1
2
3
|
[Activity(Label = "Tips004", MainLauncher = true)]
public class MainActivity : Activity
{
const int REQUEST_CODE_CARD = 1;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.ActivityMain);
var button = FindViewById<Button>(Resource.Id.myButton);
button.Click += (_, __) =>
{
var card = new Card("Yamada", 25);
var intent = new Intent(this, typeof(SecondActivity));
intent.PutExtra("data", card);
StartActivityForResult(intent, REQUEST_CODE_CARD);
};
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
switch (requestCode)
{
case REQUEST_CODE_CARD:
if (resultCode == Result.Ok && data.HasExtra("returned_data"))
{
var card = data.GetParcelableExtra("returned_data") as Card;
Toast.MakeText(this,
String.Format("{0}/{1} を受け取りました", card.Name, card.Age),
ToastLength.Long).Show();
}
break;
default:
break;
}
}
}
|
・1でStartActivity
メソッドの代わりにStartActivityForResult
メソッドを使用している。第2引数には画面ごとに一意なIDを渡す。
・2はStartActivityForResult
メソッドで遷移した画面が閉じられると、このメソッドが呼ばれる。第1引数のrequestCode
の値が1で指定されたIDであれば、目的の返り値と判断できる。第2引数resultCode
には、遷移先で設定された「結果(Result.Ok/Result.Canceled)」が格納される。実際の返り値は第3引数のdata
だ。
・3が、返り値のIntent
から実際のデータ(この例ではCard
)を取り出す処理だ。取り出すキーとなる"returned_data"
は、遷移先の画面で値を返す時に設定するキーと一致していなければならない。
続いて、遷移先であるSecondActivity
に、名前と年齢を編集して[Save]ボタンが押されたら、そのCardを「返す」処理を追加する。
1
2
|
[Activity(Label = "SecondActivity")]
public class SecondActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.ActivitySecond);
var textName = FindViewById<EditText>(Resource.Id.textName);
var textAge = FindViewById<EditText>(Resource.Id.textAge);
var intent = this.Intent;
if (intent != null && intent.HasExtra("data"))
{
var card = intent.GetParcelableExtra("data") as Card;
textName.Text = card.Name;
textAge.Text = card.Age.ToString();
}
FindViewById<Button>(Resource.Id.buttonSave).Click += (_, __) =>
{
var resultIntent = new Intent();
resultIntent.PutExtra("returned_data",
new Card(textName.Text, Int32.Parse(textAge.Text)));
SetResult(Result.Ok, resultIntent);
Finish();
};
}
}
|
・1で、返り値用のIntent
を用意している。キーは"returned_data"
とし、遷移元で受け取る処理でこれを使う。
・2で、結果のResult
と返り値を設定し、Finish
メソッドを呼ぶと、この画面が閉じられる。SetResult
せずにFinishメソッドを呼ぶと、ResultはResult.Cancel
が返される。
これでSecondActivity
での編集結果を、MainActivity
に返し、MainActivityでそれを受け取る流れが実装できた。
このプログラムを実行すると、下図のようになる。
まとめ
今回は、Xamarin.Androidで画面を追加する方法、そして画面遷移を、遷移先にデータを渡す方法、遷移先から返り値を得る方法を含めて解説した。
Xamarin.Androidでの画面の追加は、Eclipse+ADTに比べると少し手順が多いが、迷うことはないだろう。画面遷移は、ほぼネイティブと同じ仕組みであるが、遷移先にデータを渡す方法は、実装方法が少し異なるので要注意だ。
次回は、Xamarin.iOSでの画面遷移について解説する予定だ。
※以下では、本稿の前後を合わせて5回分(第1回~第5回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
1. Xamarin.Android/Xamarin.iOSを利用するには?
C#でAndroid/iOSアプリを開発できるXamarinの実践TIPSの連載がスタート。TIPSの方向性や読者対象を示し、Xamarinの概要やインストールについて解説する。
2. Xamarin.Androidで画面をレイアウトするには?
Xamarin.Androidでの画面のレイアウトの仕組みは、ネイティブのAndroidとほぼ同じ。そのレイアウト方法をネイティブでの手順と比較しながら解説する。
3. Xamarin.iOSで画面をレイアウトするには?(Xcode利用/ビルトインiOS用UIデザイナー)
iOS用の画面レイアウトを、Xcodeで行う方法を解説。また、Xamarin StudioのビルドインUIデザイナーで行う方法も説明する。
4. 【現在、表示中】≫ Xamarin.Androidで画面遷移を行うには?
Xamarin.Androidで画面を追加する方法と、2つの画面間を遷移し、遷移先にデータを渡す方法、遷移先から返り値を得る方法を解説する。
5. iOS/Androidの画面レイアウトを共通化するには?(Xamarin.Forms)
Xamarin 3がリリースされた。その新機能として注目されるXamarin.Formsの概要と、基本的な使い方、メリット/デメリットを解説する。Xamarin.Formsを使ってiOS/Android/Windows Phone間で画面レイアウトも共通化しよう。