スマートフォンではアプリの向きを制限するが、大画面デバイスでは制限しない

御社のアプリは縦向きのスマートフォンで最適に動作するため、アプリを縦向きのみに制限しているとします。しかし、横向きの大画面や折りたたみ式デバイスを開いた状態ではより多くの操作を行える可能性があります。

両方に対応するには、つまり、折りたたみ式デバイスの外側の画面ではアプリを縦向きに制限し、内側の画面では横向きを可能にするには、どうすればよいでしょうか。

このガイドは、アプリを改善してあらゆるデバイス設定を完全にサポートできるようになるまでの暫定的な対策です。

結果

アプリは、デバイスの回転に関係なく、小さい画面では縦向きのままになります。大画面では横向きと縦向きをサポートします。

バージョンの互換性

この実装はすべての API レベルに対応しています。

依存関係

Kotlin

implementation("androidx.window:window:1.5.1")
implementation("androidx.window:window-core:1.5.1")

Groovy

implementation "androidx.window:window:1.5.1"
implementation "androidx.window:window-core:1.5.1"

アプリの向きを管理する

大画面で横向きを有効にするには、アプリ マニフェストを設定して、向きの変更をデフォルトで処理するようにします。実行時にアプリのウィンドウ サイズを判別します。アプリ ウィンドウが小さい場合は、マニフェストの向きの設定をオーバーライドして、アプリの向きを制限します。

1. アプリ マニフェストで向きの設定を指定する

アプリ マニフェストの screenOrientation 要素を宣言しないか(この場合、画面の向きはデフォルトで unspecified になります)、画面の向きを fullUser に設定できます。ユーザーがセンサーベースの回転をロックしていない場合、アプリはすべてのデバイスの向きをサポートします。

<activity
    android:name=".MyActivity"
    android:screenOrientation="fullUser">

unspecifiedfullUser の違いはわずかながらも重要です。screenOrientation の値を宣言しない場合、システムによって画面の向きが選択されます。画面の向きの定義に使用されるポリシーはデバイスによって異なる場合があります。

一方、fullUser を指定すると、ユーザーがデバイスに定義した動作により近い動作になります。ユーザーがセンサーベースの回転をロックしている場合は、アプリはユーザーの設定に従います。それ以外の場合は、システムは 4 つある画面の向き(縦向き、横向き、縦向き(逆)、横向き(逆))のいずれかを許可します。

また、nosensor を使用してセンサーデータを考慮せずに向きを判断することもできますが、次のコードも同様に動作します。screenOrientation をご覧ください。

2. 画面サイズを判別する

ユーザーが許可したすべての向きサポートするようにマニフェストが設定されている場合、画面サイズに基づいてアプリの向きをプログラムで指定することができます。

モジュールの build.gradle ファイルまたは build.gradle.kts ファイルに Jetpack WindowManager ライブラリを追加します。

Kotlin

implementation("androidx.window:window:version")
implementation("androidx.window:window-core:version")

Groovy

implementation 'androidx.window:window:version'
implementation 'androidx.window:window-core:version'

Jetpack WindowManager の WindowMetricsCalculator#computeMaximumWindowMetrics() メソッドを使用して、デバイスの画面サイズを WindowMetrics オブジェクトとして取得します。このウィンドウ指標をウィンドウ サイズクラスと比較すると、画面の向きを制限するタイミングを判断できます。

ウィンドウ サイズクラスは、小さい画面と大画面の間のブレークポイントを提供します。

WindowSizeClass#minWidthDp ブレークポイントと WindowSizeClass#minHeightDp ブレークポイントを使用して、画面サイズを決定します。

/** Determines whether the device has a compact screen. **/
fun compactScreen() : Boolean {
    val metrics = WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(this)
    val width = metrics.bounds.width()
    val height = metrics.bounds.height()
    val density = resources.displayMetrics.density
    val windowSizeClass =
        BREAKPOINTS_V1.computeWindowSizeClass(width / density, height / density)
    return windowSizeClass.minWidthDp == 0
}
    注:
  • 上記の例は、アクティビティのメソッドとして実装されています。そのため、アクティビティは computeMaximumWindowMetrics() の引数で this として逆参照されています。
  • アプリがマルチ ウィンドウ モードで起動される可能性があるため、computeCurrentWindowMetrics() の代わりに computeMaximumWindowMetrics() メソッドが使用されています。マルチ ウィンドウ モードの場合、画面の向きの設定は無視されます。アプリ ウィンドウがデバイスの画面全体になっていない限り、アプリ ウィンドウのサイズを判別して向きの設定をオーバーライドしても意味がありません。

アプリ内で computeMaximumWindowMetrics() メソッドを使用できるように依存関係を宣言する手順については、WindowManager をご覧ください。

3. アプリ マニフェストの設定をオーバーライドする

デバイスの画面サイズが小さいと判断した場合は、Activity#setRequestedOrientation() を呼び出してマニフェストの screenOrientation 設定をオーバーライドできます。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    requestedOrientation = if (compactScreen())
        ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else
        ActivityInfo.SCREEN_ORIENTATION_FULL_USER
    ...
    // Replace with a known container that you can safely add a
    // view to where the view won't affect the layout and the view
    // won't be replaced.
    val container: ViewGroup = binding.container

    // Add a utility view to the container to hook into
    // View.onConfigurationChanged. This is required for all
    // activities, even those that don't handle configuration
    // changes. You can't use Activity.onConfigurationChanged,
    // since there are situations where that won't be called when
    // the configuration changes. View.onConfigurationChanged is
    // called in those scenarios.
    container.addView(object : View(this) {
        override fun onConfigurationChanged(newConfig: Configuration?) {
            super.onConfigurationChanged(newConfig)
            requestedOrientation = if (compactScreen())
                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else
                ActivityInfo.SCREEN_ORIENTATION_FULL_USER
        }
    })
}

onCreate() メソッドと View.onConfigurationChanged() メソッドにロジックを追加することで、アクティビティがサイズ変更されたりディスプレイ間を移動されたりするたびに(デバイスの回転後、折りたたみ式デバイスが折りたたまれたとき / 広げられたとき、など)、最大ウィンドウ指標を取得して向きの設定をオーバーライドできるようになります。構成の変更が発生するタイミングや、その変更によってアクティビティの再作成が発生するタイミングについて詳しくは、構成の変更に対処するをご覧ください。

Jetpack Compose を使用している場合は、アプリのルート コンポーザブルで同じ compactScreen() 関数を使用して、同じ結果を得ることができます。

要点

  • screenOrientation: デバイスの向きの変化にアプリがどう反応するかを指定できるアプリ マニフェストの設定
  • Jetpack WindowManager: アプリ ウィンドウのサイズとアスペクト比を判別できるライブラリのセット。API レベル 14 との下位互換性がある
  • Activity#setRequestedOrientation(): 実行時にアプリの向きを変更できるメソッド

このガイドを含むコレクション

このガイドは、Android 開発のより広範な目標を対象とした、厳選されたクイックガイド コレクションの一部です。

タブレット、折りたたみ式デバイス、ChromeOS デバイスで最適化されたユーザー エクスペリエンスをサポートするようにアプリを有効にします。

ご質問やフィードバックがある場合

よくある質問のページでクイック ガイドを確認するか、お問い合わせのうえご意見をお聞かせください。