UI をレスポンシブ レイアウトに移行する

Android アプリは、拡大し続けるデバイス フォーム ファクタのエコシステムをサポートする必要があります。アプリの UI は、さまざまな画面サイズ、画面の向き、デバイスの状態に適合するようレスポンシブである必要があります。

レスポンシブ UI は、柔軟性と継続性の原則を重視しています。

柔軟性とは、レイアウトが利用可能なスペースを最大限に活用し、そのスペースの変化に合わせて調整されることを指します。調整はさまざまな形で行われます。単に 1 つのビューのサイズを大きくする、アクセスしやすい場所にビューを再配置する、追加のビューを表示 / 非表示にする、またはこれらの組み合わせです。

継続性とは、あるウィンドウ サイズから別のウィンドウ サイズに移行する際にシームレスなユーザー エクスペリエンスを実現することです。ユーザーが何をしていても、それが遮られることなく継続する必要があります。サイズの変更は、ビュー階層全体の破棄と再作成を伴うことがあるため、ユーザーが場所を見失ったりデータを失ったりしないようにすることが重要です。

非推奨事項

レイアウトに関する決定を行う際は、物理的なハードウェア値を使用しないでください。固定値に基づいて決定したくなるものですが、そうした値は多くの場合、UI が使用できるスペースの決定には有用ではありません。

タブレットでは、アプリがマルチウィンドウ モードで実行される(つまり、アプリが別のアプリと画面を共有する)場合があります。ChromeOS では、サイズ変更可能なウィンドウでアプリが動作する場合があります。折りたたみ式デバイスや複数のディスプレイを備えたデバイスなど、複数の物理画面が存在することもあります。いずれの場合も、物理画面サイズはコンテンツの表示方法の決定とは無関係です。

複数のデバイスでサイズの異なるアプリ ウィンドウを表示。
図 1. ウィンドウ サイズは、デバイスやディスプレイの物理的なサイズと異なる場合がある。

同じ理由で、アプリを特定の画面の向きまたはアスペクト比に固定しないでください。デバイス自体が特定の画面の向きになっていても、アプリはそのウィンドウ サイズのみに基づいて異なる画面の向きになることがあります。たとえばマルチウィンドウ モードを使用している横向きのタブレットでは、幅より高さの方が大きいため、アプリが縦向きで表示される可能性があります。

また、デバイスがスマートフォンなのかタブレットなのかを判定しようとしないでください。具体的にどのようなものをタブレットとするかは、サイズ、アスペクト比、またはそれらの組み合わせに一定の基準があるわけではなく、やや主観的です。新しいフォーム ファクタが登場すると、こうした前提条件が変わる場合があり、この違いは重要ではなくなります。

前述の方法ではなく、ブレークポイントとウィンドウ サイズクラスを使用してください。

ブレークポイントとウィンドウ サイズクラス

アプリに割り当てられる画面の実際の領域は、アプリのウィンドウです。画面の全体を占有する場合もあれば、一部しか占有しない場合もあるため、アプリのレイアウトを大まかに決める際はウィンドウ サイズを使用します。

複数のフォーム ファクタ向けに設計する場合は、レイアウトの大まかな決定において分岐条件となるしきい値を見つけます。このため、マテリアル デザインのレスポンシブ レイアウト グリッドには幅と高さのブレークポイントがあり、未加工のサイズをウィンドウ サイズクラスという個別の標準化されたグループにマッピングできます。縦方向のスクロールが多いことから、ほとんどのアプリは主に幅のサイズクラスを重視しています。そのため、ほとんどのアプリはブレークポイントをいくつか処理するだけで、すべての画面サイズに最適化できます(ウィンドウ サイズクラスの詳細については、各種の画面サイズのサポートをご覧ください)。

永続的な UI 要素

マテリアル デザインのレイアウト ガイドラインには、アプリバー、ナビゲーション、コンテンツの領域が定義されています。最初の 2 つは通常、ビュー階層のルート(またはその近く)にある永続的な UI 要素です。なお「永続的」とは、必ずしもビューが常に表示されるということではなく、他のコンテンツ ビューが移動または変化しても同じ位置に留まるという意味です。たとえば、ナビゲーション要素がスライド式のドロワーに入っており、画面外にあるがドロワーは常にそこにある、という場合です。

永続的な要素はレスポンシブにできます。通常はウィンドウの幅全体または高さ全体を占めるため、サイズクラスを使用して配置場所を決定することをおすすめします。これにより、コンテンツのために残されたスペースが明確になります。次のスニペットでは、アクティビティはコンパクトな画面にはボトムバーを、大きい画面にはトップ アプリバーを使用しています。条件付きのレイアウトは、前述の幅ブレークポイントを使用しています。

<!-- res/layout/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- content view(s) -->

    <com.google.android.material.bottomappbar.BottomAppBar
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        ... />
</androidx.constraintlayout.widget.ConstraintLayout>


<!-- res/layout-w600dp/main_activity.xml -->
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        ... />

    <!-- content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>

コンテンツ

永続的な UI 要素を配置したら、アプリのナビゲーション グラフで NavHostFragment を使用するなどして、残りのスペースをコンテンツに使用します。その他の考慮事項については、レスポンシブ UI のナビゲーションをご覧ください。

さまざまなサイズですべてのデータを使用できるようにする

現在ほとんどのアプリ フレームワークは、UI に寄与する Android コンポーネント(アクティビティ、フラグメント、ビュー)とは別のデータモデルを使用しています。Jetpack では通常、ViewModel がこの役割を果たし、構成の変更後も存続できるというメリットがあります(詳細については ViewModel の概要をご覧ください)。

さまざまなサイズに適応するレイアウトを実装する場合、現在のサイズに基づいて別のデータモデルを使用したくなるかもしれません。しかし、これは単方向データフローの原則に反します。データはビューに対して下向きに流れ、ユーザー操作などのイベントは上向きに流れるようにする必要があります。データモデルが UI レイヤの構成に依存するという逆方向の依存関係を作成すると、これは非常に複雑になります。アプリのサイズが変更された場合、データモデルを別のデータモデルに変換することを考慮する必要があります。

代わりに、データモデルが最大のサイズクラスに対応できるようにし、UI でコンテンツを選択的に表示、非表示、または再配置して、現在のサイズクラスに適応させます。サイズクラス間の移行時にレイアウトをどのように動作させるかを決定する際に使用できる戦略を以下に示します。

コンテンツを拡大する

正規レイアウト: フィード

スペースの拡大は、単にいろいろなものを大きくするほか、アクセスしやすくなるようコンテンツを再フォーマットする機会になります。

コレクションを大きくする。多くのアプリは、RecyclerViewScrollView など、スクロールするコンテナにアイテムのコレクションを表示します。コンテナが自動的に大きくなるようにすると、表示できるコンテンツが増えます。ただし、コンテナ内のコンテンツが過度に引き伸ばされたり、歪んだりしないように注意してください。たとえば RecyclerView で、幅がコンパクトでないときは GridLayoutManagerStaggeredGridLayoutManagerFlexboxLayout のような別のレイアウト マネージャーを使用することを検討してください。

デバイスを折りたたんだり広げたりしたときに、幅のサイズクラスに基づき、レイアウト マネージャーによるアプリ レイアウトがどのように異なるのかを示しています。
図 2. ウィンドウ サイズクラスごとに異なるレイアウト マネージャー。

個々のアイテムごとにサイズや形状を変えることで、表示されるコンテンツを増やし、アイテムの境界をわかりやすくすることもできます。

ヒーロー要素を強調する。レイアウトに特定の焦点(画像や動画など)がある場合、アプリのウィンドウが大きくなったときに拡大して、ユーザーの注目を維持します。その他の補助的な要素は、ヒーロービューの周囲または下部に再配置できます。

このようなレイアウトはさまざまな方法で作成できますが、ConstraintLayout は、子ビューのサイズを制限(割合による制限、アスペクト比の適用など)する方法と、子を自身や他の子に対して相対的に配置する方法が数多く用意されているため、特にこの目的に適しています。こうした機能の詳細については、ConstraintLayout でレスポンシブ UI を作成するをご覧ください。

デフォルトで折りたたみ可能なコンテンツを表示する。スペースに余裕がない場合にはタップ、スクロール、ジェスチャーなどの操作をしないと表示されないコンテンツを、スペースに余裕がある場合には表示します。たとえば、コンパクトなときはタブ形式のインターフェースに表示されるコンテンツを、スペースに余裕があるときは列やリストに再配置できます。

余白を拡大する。スペースが大きすぎるためにコンテンツをすべて利用しても魅力的なフィット感が得られない場合は、レイアウトの余白を広げることで、コンテンツを中央に保持し、個々のビューのサイズと間隔が自然になるようにします。

あるいは、全画面コンポーネントをフローティング ダイアログ UI に変換することもできます。これは特に、メールの作成、カレンダーの予定の作成など、即時のユーザータスクを行うために専用のフォーカスを必要とするコンポーネントに適しています。

ダイアログを全画面表示する標準的なスマートフォンと、同じダイアログをフローティング ウィンドウで表示する折りたたみ式スマートフォン。
図 3. 全画面ダイアログを拡大幅の中央の標準ダイアログに変換。

コンテンツを追加

正規レイアウト: 補助ペイン、リストの詳細ビュー

補助ペインを使用する。補助ペインは、ドキュメント内のコメントや再生リスト内のアイテムなど、主要なコンテンツに関連する追加のコンテンツまたはコンテキスト アクションを表示します。補助ペインは通常、画面の下 3 分の 1 を拡大高さに使用し、後端の 3 分の 1 を拡大幅に使用します。

重要な検討事項は、ペインを表示する十分なスペースがない場合に、このコンテンツをどこに配置するかということです。代替案は次のとおりです。

  • DrawerLayout を使用した後端のサイドドロワー
  • BottomSheetBehavior を使用したボトムドロワー
  • メニュー アイコンのタップでアクセスできるメニューまたはポップアップ ウィンドウ
図 4. 補助ペインに追加コンテンツを表示する別の方法。

2 ペイン レイアウトを作成する。小画面なら通常は個別に表示する機能を、大画面では組み合わせて表示できる場合があります。多くのアプリで一般的な操作パターンは、連絡先や検索結果などのアイテムのリストを表示し、アイテムが選択されたときにアイテムの詳細に切り替える、というものです。大画面用にリストを拡大するのではなく、リストの詳細ビューを使用して、2 ペイン レイアウトで両方の機能を並べて表示します。補助ペインとは異なり、リストの詳細ビューの詳細ペインは、小画面でも独立して表示できるスタンドアロン要素です。

リストの詳細ビューを実装するには、SlidingPaneLayout 専用ウィジェットを使用します。このウィジェットは、2 つのペインに指定された layout_width 値に基づいて、両方のペインを一緒に表示する十分なスペースがあるかどうかを自動的に計算します。また、残りのスペースは layout_weight を使用して分配できます。十分なスペースがない場合、各ペインはレイアウトの全幅を使用し、詳細ペインは画面外またはリストペインの前面にスライドします。

幅の広いディスプレイを備えたデバイスで、リスト詳細のレイアウトの両ペインを表示する SlidingPaneLayout。
図 5. 拡大幅で 2 つのペインを表示し、コンパクトな幅で 1 つのペインを表示する SlidingPaneLayout。

SlidingPaneLayout の使用方法について詳しくは、2 ペインのレイアウトを作成するをご覧ください。なお、このパターンはナビゲーション グラフの構造に影響を与える可能性があります(レスポンシブ UI のナビゲーションをご覧ください)。

参考情報