回転の処理

このトピックでは、Xamarin.Android でデバイスの向きの変更を処理する方法について説明します。 Android リソース システムを使用して、特定のデバイスの向きのリソースを自動的に読み込む方法と、向きの変更をプログラムで処理する方法について説明します。

概要

モバイル デバイスは簡単に回転できるため、組み込みの回転はモバイル OS の標準的な機能です。 Android には、ユーザー インターフェイスが XML で宣言的に作成されるか、コード内でプログラムによって作成されるかに関係なく、アプリケーション内で回転を処理するための高度なフレームワークが用意されています。 回転されたデバイスで宣言型レイアウトの変更を自動的に処理する場合、アプリケーションは Android リソース システムとの緊密な統合の恩恵を受けることができます。 プログラムによるレイアウトの場合、変更は手動で処理する必要があります。 これにより、実行時の細かい制御が可能になりますが、開発者にとってより多くの作業が必要になります。 アプリケーションでは、Activity の再起動をオプトアウトし、方向の変更を手動で制御することもできます。

このガイドでは、次の向きに関するトピックについて説明します。

  • 宣言型のレイアウト回転 – Android リソース システムを使用して、特定の向きのレイアウトとドローアブルの両方を読み込む方法など、向きに対応するアプリケーションを構築する方法。

  • プログラムによるレイアウト回転 – プログラムによってコントロールを追加する方法と、方向の変更を手動で処理する方法。

レイアウトを使用して宣言的に回転を処理する

名前付け規則に従ったフォルダーにファイルを格納することで、Android は向きが変わったときに適切なファイルを自動的に読み込みます。 これには、次のサポートが含まれます。

  • レイアウト リソース – 向きごとにどのレイアウト ファイルを拡張するかを指定します。

  • ドローアブル リソース – 各方向で読み込まれるドローアブルを指定します。

レイアウト リソース

既定では、Resources/layout フォルダーに含まれる Android XML (AXML) ファイルが、Activity のビューのレンダリングに使用されます。 このフォルダーのリソースは、横向き専用に追加のレイアウト リソースが提供されていない場合に、縦向きと横向きの両方に使用されます。 既定のプロジェクト テンプレートによって作成されるプロジェクト構造を考えてみましょう。

Default project template structure

このプロジェクトは、Resources/layout フォルダーに 1 つの Main.axml ファイルを作成します。 Activity の OnCreate メソッドが呼び出されると、次の XML に示すようにボタンを宣言している Main.axml に定義されているビューが拡張されます。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
<Button  
  android:id="@+id/myButton"
  android:layout_width="fill_parent" 
  android:layout_height="wrap_content" 
  android:text="@string/hello"/>
</LinearLayout>

デバイスが横向きに回転された場合、Activity の OnCreate メソッドが再度呼び出され、次のスクリーンショットに示すように、同じ Main.axml ファイルが拡張されます。

Same screen but in landscape orientation

向き固有のレイアウト

レイアウト フォルダー (既定では縦長に設定され、layout-land という名前のフォルダーを含めることで明示的に layout-port という名前を付けることもできます) に加えて、アプリケーションでは、コードを変更することなく、横向きの場合に必要なビューを定義できます。

Main.axml ファイルに次の XML が含まれているとします。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <TextView
    android:text="This is portrait"
    android:layout_height="wrap_content"
    android:layout_width="fill_parent" />
</RelativeLayout>

追加の Main.axml ファイルを含む layout-land という名前のフォルダーがプロジェクトに追加された場合、横向きのときにレイアウトを拡張すると、Android は新しく追加された Main.axml ファイルを読み込みます。次のコードを含む Main.axml ファイルの横向きバージョンを考えてみましょう (わかりやすくするため、この XML はコードの既定の縦向きバージョンに似ていますが、TextView では別の文字列を使用します)。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <TextView
    android:text="This is landscape"
    android:layout_height="wrap_content"
    android:layout_width="fill_parent" />
</RelativeLayout>

次に示すように、このコードを実行し、デバイスを縦から横に回転するとことで、新しい XML 読み込みが示されます。

Portrait and landscape screenshots printing the portrait mode

ドローアブル リソース

回転中、Android はドローアブル リソースをレイアウト リソースと同様に扱います。 この場合、システムは、ドローアブルをそれぞれ Resources/drawableResources/drawable-land フォルダーからを取得します。

たとえば、プロジェクトの Resources/drawable フォルダーに Monkey.png という名前のイメージが含まれているとします。ここで、ドローアブルは次のように XML 内の ImageView から参照されます。

<ImageView
  android:layout_height="wrap_content"
  android:layout_width="wrap_content"
  android:src="@drawable/monkey"
  android:layout_centerVertical="true"
  android:layout_centerHorizontal="true" />

さらに、別のバージョンの Monkey.pngResources/drawable-land に含まれているとします。 レイアウト ファイルと同様に、デバイスが回転すると、次に示すように、ドローアブルは特定の向きに合わせて変化します。

Different version of Monkey.png shown in portrait and landscape modes

プログラムによる回転の処理

コードでレイアウトを定義する場合があります。 これは、技術的な制限、開発者の好みなど、さまざまな理由で発生する可能性があります。プログラムによってコントロールを追加する場合、アプリケーションはデバイスの向きを手動で考慮する必要があります。これは、XML リソースを使用するときには自動的に処理されます。

コードでのコントロールの追加

プログラムでコントロールを追加するには、アプリケーションで次の手順を実行する必要があります。

  • レイアウトを作成する。
  • レイアウト パラメーターを設定する。
  • コントロールを作成する。
  • コントロール レイアウト パラメーターを設定する。
  • レイアウトにコントロールを追加する。
  • レイアウトをコンテンツ ビューとして設定する。

たとえば、次のコードに示すように、RelativeLayout に追加された 1 つの TextView コントロールで構成されるユーザー インターフェイスを考えてみましょう。

protected override void OnCreate (Bundle bundle)
{
  base.OnCreate (bundle);
                        
  // create a layout
  var rl = new RelativeLayout (this);

  // set layout parameters
  var layoutParams = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.FillParent);
  rl.LayoutParameters = layoutParams;
        
  // create TextView control
  var tv = new TextView (this);

  // set TextView's LayoutParameters
  tv.LayoutParameters = layoutParams;
  tv.Text = "Programmatic layout";

  // add TextView to the layout
  rl.AddView (tv);
        
  // set the layout as the content view
  SetContentView (rl);
}

このコードは、RelativeLayout クラスのインスタンスを作成し、その LayoutParameters プロパティを設定します。 LayoutParams クラスは、再利用可能な方法でコントロールを配置する方法をカプセル化する Android の手段です。 レイアウトのインスタンスが作成されたら、コントロールを作成して追加できます。 コントロールには、この例の TextView のような LayoutParameters もあります。 TextView の作成後、RelativeLayout に追加し、RelativeLayout をコンテンツ ビューとして設定すると、次のようにアプリケーションに TextView が表示されます。

Increment counter button shown in both portrait and landscape modes

コードでの方向の検出

OnCreate が呼び出されたとき (これはデバイスが回転するたびに発生します) に、アプリケーションで向きごとに異なるユーザー インターフェイスを読み込もうとする場合、向きを検出し、目的のユーザー インターフェイス コードを読み込む必要があります。 Android には、次に示すように、WindowManager.DefaultDisplay.Rotation プロパティを使用して現在のデバイスの回転を判別するために使用できる、WindowManager というクラスがあります。

protected override void OnCreate (Bundle bundle)
{
  base.OnCreate (bundle);
                        
  // create a layout
  var rl = new RelativeLayout (this);

  // set layout parameters
  var layoutParams = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.FillParent);
  rl.LayoutParameters = layoutParams;
                        
  // get the initial orientation
  var surfaceOrientation = WindowManager.DefaultDisplay.Rotation;
  // create layout based upon orientation
  RelativeLayout.LayoutParams tvLayoutParams;
                
  if (surfaceOrientation == SurfaceOrientation.Rotation0 || surfaceOrientation == SurfaceOrientation.Rotation180) {
    tvLayoutParams = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
  } else {
    tvLayoutParams = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
    tvLayoutParams.LeftMargin = 100;
    tvLayoutParams.TopMargin = 100;
  }
                        
  // create TextView control
  var tv = new TextView (this);
  tv.LayoutParameters = tvLayoutParams;
  tv.Text = "Programmatic layout";
        
  // add TextView to the layout
  rl.AddView (tv);
        
  // set the layout as the content view
  SetContentView (rl);
}

このコードは、次に示すように、横向きに回転すると、画面の左上から 100 ピクセルの位置に TextView を設定し、新しいレイアウトへと自動的にアニメーション表示されます。

View state is preserved across portrait and landscape modes

Activity の再起動の防止

アプリケーションでは、OnCreate のすべての処理に加えて、次のように ActivityAttributeConfigurationChanges を設定することで、方向が変更されたときに Activity が再起動されないようにすることもできます。

[Activity (Label = "CodeLayoutActivity", ConfigurationChanges=Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize)]

これにより、デバイスが回転されたときに、Activity は再起動されません。 この場合に方向の変更を手動で処理するには、Activity で OnConfigurationChanged メソッドをオーバーライドし、以下の Activity の新しい実装のように、渡される Configuration オブジェクトから向きを識別できます。

[Activity (Label = "CodeLayoutActivity", ConfigurationChanges=Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize)]
public class CodeLayoutActivity : Activity
{
  TextView _tv;
  RelativeLayout.LayoutParams _layoutParamsPortrait;
  RelativeLayout.LayoutParams _layoutParamsLandscape;
                
  protected override void OnCreate (Bundle bundle)
  {
    // create a layout
    // set layout parameters
    // get the initial orientation

    // create portrait and landscape layout for the TextView
    _layoutParamsPortrait = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
                
    _layoutParamsLandscape = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
    _layoutParamsLandscape.LeftMargin = 100;
    _layoutParamsLandscape.TopMargin = 100;
                        
    _tv = new TextView (this);
                        
    if (surfaceOrientation == SurfaceOrientation.Rotation0 || surfaceOrientation == SurfaceOrientation.Rotation180) {
      _tv.LayoutParameters = _layoutParamsPortrait;
    } else {
      _tv.LayoutParameters = _layoutParamsLandscape;
    }
                        
    _tv.Text = "Programmatic layout";
    rl.AddView (_tv);
    SetContentView (rl);
  }
                
  public override void OnConfigurationChanged (Android.Content.Res.Configuration newConfig)
  {
    base.OnConfigurationChanged (newConfig);
                        
    if (newConfig.Orientation == Android.Content.Res.Orientation.Portrait) {
      _tv.LayoutParameters = _layoutParamsPortrait;
      _tv.Text = "Changed to portrait";
    } else if (newConfig.Orientation == Android.Content.Res.Orientation.Landscape) {
      _tv.LayoutParameters = _layoutParamsLandscape;
      _tv.Text = "Changed to landscape";
    }
  }
}

ここでは、TextView's レイアウト パラメーターは横向きと縦向きの両方に対して初期化されています。 方向が変わったときに Activity は再作成されないため、クラス変数には TextView 自体と共にパラメーターが保持されます。 このコードでは引き続き OnCreatesurfaceOrientartion を使用して、TextView の初期レイアウトを設定します。 その後、OnConfigurationChanged により後続のすべてのレイアウト変更が処理されます。

アプリケーションを実行すると、デバイスの回転が発生したときに Android によってユーザー インターフェイスの変更が読み込まれ、Activity は再起動されません。

宣言型レイアウトで Activity の再起動を防止する

XML でレイアウトを定義することでも、デバイスの回転によって発生する Activity の再起動を防止できます。 たとえば、Activity の再起動を (パフォーマンス上の理由から) 防ぐ必要があり、異なる向きのために新しいリソースを読み込む必要がない場合は、このアプローチを使用できます。

これを行うには、プログラムによるレイアウトで使用するのと同じ手順に従います。 前に CodeLayoutActivity で行ったように、ActivityAttributeConfigurationChanges を設定します。 方向の変更のために実行する必要があるコードは、ここでも OnConfigurationChanged メソッドに実装できます。

向きの変更中の状態の維持

回転を宣言的に処理するかプログラムによって処理するかに関係なく、すべての Android アプリケーションでは、デバイスの向きが変化したときに状態を管理するための同じ手法を実装する必要があります。 Android デバイスが回転すると、システムにより実行中の Activity が再起動されるため、状態の管理は重要です。 Android でこれが実施されるのは、特定の向き専用に設計されたレイアウトやドローアブルなどの代替リソースを簡単に読み込むことができるようにするためです。 再起動されると、Activity はローカル クラス変数に格納されている可能性のある一時的な状態をすべて失います。 そのため、Activity が状態に依存している場合は、その状態をアプリケーション レベルで保持する必要があります。 アプリケーションは、向きの変更をまたいで保持する必要があるアプリケーションの状態の保存と復元を処理する必要があります。

Android での状態の永続化の詳細については、アクティビティ ライフサイクルのガイドを参照してください。

まとめ

この記事では、Android の組み込み機能を使用して回転を処理する方法について説明しました。 まず、Android リソース システムを使用して回転に対応したアプリケーションを作成する方法について説明しました。 次に、コードでコントロールを追加する方法と、方向の変更を手動で処理する方法について説明しました。