Xamarin逆引きTips

Xamarin逆引きTips

Xamarin.Androidで画面遷移を行うには?

2014年5月28日

Xamarin.Androidで画面を追加する方法と、2つの画面間を遷移し、遷移先にデータを渡す方法、遷移先から返り値を得る方法を解説する。

奥山 裕紳(@amay077
  • このエントリーをはてなブックマークに追加

 Xamarin.Androidで画面遷移を行う方法を解説する。また、次の画面にデータを渡す方法、遷移した画面から結果を返してもらう方法についても述べる。

画面の新規作成

 「Tips:Xamarin.Androidで画面をレイアウトするには?」では、新しい画面の作成には触れなかったので、ここで解説しておこう。なお、本Tipsで使うプロジェクトは「Android Ice Cream Sandwich Application」(プロジェクト名:「Tips004」)で作成したものを使用する。

1Activityクラスを追加する

 [ファイル]メニュー から、[新規]-[ファイル]と選択して[新しいファイル]ダイアログを表示し、その中から[Android]-[Android Activity]を選択する。そしてクラス名をここではSecondActivityと入力して[新規]ボタンを押す。

図1 Activity追加画面

 なお、以前解説した通り、AndroidManifest.xmlファイルへの記述は必要ない。

2レイアウト定義ファイルを追加する

 続いて、レイアウト定義の.axmlファイルを追加する。

 再度、[新しいファイル]ダイアログを開き、その中から[Android]-[Android Layout]を選択する。レイアウトファイル名はActivitySecondとして[新規]ボタンを押す。

図2 レイアウト定義ファイル画面

3Activityクラスとレイアウト定義ファイルを関連付ける

 ここまでで、プロジェクトにSecondActivity.csファイルとActivitySecond.axmlファイルが追加されているはずだ。だが、両者はまだ関連付いていないので、SecondActivityクラスにActivitySecond.axmlファイルを読み込むコードを追加する必要がある(ネイティブのEclipse+ADTでは、この手順を一括で行ってくれるので、Xamarin Studioはそれと比べると機能的に見劣りする)。

 SecondActivity.csファイルのOnCreateメソッドに、以下のように1行追加する。

C#
[Activity(Label = "SecondActivity")]
public class SecondActivity : Activity
{
  protected override void OnCreate(Bundle bundle)
  {
    base.OnCreate(bundle);

    SetContentView(Resource.Layout.ActivitySecond);   // <--追加
  }
}
Activityクラスとレイアウト定義ファイルを関連付ける(SecondActivity.cs)

単純な画面遷移

 Xamarin.Androidでも、ネイティブ同様、「インテント」を利用して画面遷移を行う。

 MainActivityのボタンを押すと、SecondActivityに遷移するコードは以下のようになる。

C#
[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);
    };
  }
}
メイン画面からセカンド画面に遷移するコード(MainActivity.cs)

 ネイティブの開発者にはおなじみのコードだろう。遷移先Activityの型の指定が、Java言語ではSecondActivity.classとするのに対して、C#ではtypeof(SecondActivity)とするのが異なるだけだ。

遷移先の画面にデータを渡す方法

 次に、遷移元の画面からデータを渡しながら遷移する方法を解説する。

1必要なアセンブリの参照を追加する

 アセンブリとは、.NETの世界の「ライブラリ」のことだ、Javaの.jarのようなものだ。Xamarin.Androidでは、画面間でデータを渡すのに必要な機能を別なアセンブリに分けており、それは既定のプロジェクトには含まれていないので追加する必要がある。

 手順は、メニューバーから[プロジェクト]-[参照アセンブリの編集]と選択して[Edit References]ダイアログを表示し、[Packages]タブの中から[Mono.Android.Export]にチェックを入れ[OK]ボタンを押す。

図3 アセンブリ参照追加 画面

2データクラスを作成する

 さて、では画面間でやりとりされるデータを表すクラスを作成する。

 Androidでは、このような画面と画面(あるいは画面とサービスなど)の行き来は、同じプロセス内で行われるとは限らないため、その間で扱うデータはParcel(=英単語の意味は「小包」で、つまり小さなデータなどを包むためのメッセージング用のコンテナー)に保存できなければならない。「Parcelに保存できる」データであることを示すインターフェースがParcelabelだ。

 画面から画面へ受け渡すデータクラスは、このParcelableを実装する必要がある。まずはその方法を見てみよう。

Parcelableの実装方法(Java言語でのネイティブ実装の例)

 データクラスとして、Name(名前)とAge(年齢)を持つCard(カード)クラスを作成する。なお、このパートは、ネイティブとXamarin.Androidとで実装方法がやや異なるので、両者を比較しながら解説する。

 Parcelableインターフェースを実装したCardクラスは、ネイティブ(Java言語)では下のようになる。

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へ読み書きできるCardクラスの例(Card.java)

・このクラスのデータを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ファイルを追加して、下記のコードを記述する)。

C#
 
 
 
 
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];
    }
  }
}
……省略……
Parcelへ読み書きできるCardクラスの例(Card.cs)

1は、JavaではParcelableだったインターフェースが、.NETの命名規則に従ったIParcelableになっているのはささいなことだが、Java.Lang.Objectクラスから派生させている点に注目したい。画面遷移はXamarin.AndroidでなくAndroid OSで行われる処理であり、そこで扱われるデータにはJavaオブジェクトが必要だからだ。
2Card.javaファイルの実装例と大きく異なる点だ。ネイティブでは、静的なCREATORフィールドを利用するが、Xamarin.AndroidではこのようにExportField属性を付与した静的メソッドでIPercelableCreatorオブジェクトを返却することで実現する。
・C#言語では、Java言語のように匿名クラスを使用できないので、別途、内部クラスとしてCardParcelableCreatorクラス(=3)を用意する。注意すべきは、CreateFromParcelメソッドの戻り値がJava.Lang.Objectであり、ジェネリクス(Generics/ジェネリック)による型強制がないので、返却するオブジェクトを間違えないようにすることだ。

 このように、Xamarin.Androidで画面間で扱われるデータクラスの実装方法は、ネイティブと少し異なるので注意が必要だ。また、このような名前と年齢しか持たないデータクラスは、プリミティブな(=基本データ型しか使用していない単純な)クラスでありたいものだが、Androidの画面遷移においては、OSの仕組みに強く依存しているので、上記のコード例のようにAndroid.OSJava.Interop名前空間への参照が必要だ。そのため、このCardクラスは(Xamarin.Androidではない)Xamarin.iOSでは使用できない。クロスプラットフォーム開発をする際に考慮すべき点となるので、覚えておいてほしい。

3画面遷移時にデータを渡す

 データクラスが実装できれば、あとはそのインスタンスを画面遷移時に渡すだけだ。以下のようなコードとなる。

C#
 
 
 
 
 
 
 
 
 
 
 
 
 
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);
    };
  }
}
データを渡すコードを追加したMainActivity.cs

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にならって設定してほしい。

図4 SecondActivityのレイアウト画面

 そして、SecondActivity.csファイルのOnCreateメソッドでデータを受け取る。以下のようなコードだ。

C#
 
 
 
 
 
 
 
 
 
 
 
 
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();
    }
  }
}
データを受け取るコードを追加したSecondActivity.cs

1で、Intentプロパティに、データが渡されているかをチェックする。Activity.IntentプロパティはネイティブではActivity.getIntent()メソッドに相当するものだ。Xamarin.Androidではこのようにgetter/setterメソッドは、プロパティに置き換えられている。
2で、渡されたデータを取得している。as句は、その後ろに指定する型に変換できなければnullを返すC#の言語機能だ。

 このプログラムを実行すると、下図のような結果になる。

図5 データを渡して画面遷移を行った例

遷移先の画面からデータを返す方法

 最後に、遷移したSecondActivityから、遷移元のMainActivityにデータを返す方法を解説する。

 Androidでは、遷移元の画面が、遷移先からの返り値を得るには、StartActivityメソッドではなくStartActivityForResultメソッドを使う必要がある。まずは、MainActivity.csファイルを以下のように修正する。

C#
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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;
    }
  }
}
データを受け取るコードを追加したMainActivity.cs

1StartActivityメソッドの代わりにStartActivityForResultメソッドを使用している。第2引数には画面ごとに一意なIDを渡す。
2StartActivityForResultメソッドで遷移した画面が閉じられると、このメソッドが呼ばれる。第1引数のrequestCodeの値が1で指定されたIDであれば、目的の返り値と判断できる。第2引数resultCodeには、遷移先で設定された「結果(Result.Ok/Result.Canceled)」が格納される。実際の返り値は第3引数のdataだ。
3が、返り値のIntentから実際のデータ(この例ではCard)を取り出す処理だ。取り出すキーとなる"returned_data"は、遷移先の画面で値を返す時に設定するキーと一致していなければならない。

 続いて、遷移先であるSecondActivityに、名前と年齢を編集して[Save]ボタンが押されたら、そのCardを「返す」処理を追加する。

C#
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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();
    };
  }
}
データを受け取るコードを追加したSecondActivity.cs

1で、返り値用のIntentを用意している。キーは"returned_data"とし、遷移元で受け取る処理でこれを使う。
2で、結果のResultと返り値を設定し、Finishメソッドを呼ぶと、この画面が閉じられる。SetResultせずにFinishメソッドを呼ぶと、ResultはResult.Cancelが返される。

 これでSecondActivityでの編集結果を、MainActivityに返し、MainActivityでそれを受け取る流れが実装できた。

 このプログラムを実行すると、下図のようになる。

図6 遷移先からデータを返された例

まとめ

 今回は、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間で画面レイアウトも共通化しよう。

サイトからのお知らせ

Twitterでつぶやこう!