各種の画面サイズのサポート

各種の画面サイズをサポートすると、アプリを数多くのユーザーのさまざまなデバイスで利用できるようになります。

できるだけ多くの画面サイズをサポートするには、アプリのレイアウトがレスポンシブ / アダプティブになるように設計する必要があります。レスポンシブ / アダプティブ レイアウトは、画面サイズにかかわらず、最適なユーザー エクスペリエンスを提供します。これにより、アプリはスマートフォン、タブレット、折りたたみ式デバイス、Chrome OS デバイス、縦向きと横向き、サイズ変更が可能な構成(マルチ ウィンドウ モードなど)に対応できます。

ウィンドウ サイズクラス

ウィンドウ サイズクラスは、アプリのレスポンシブ レイアウト、アダプティブ レイアウトを設計、開発、テストするための、独自のビューポート ブレークポイントのセットです。これらのブレークポイントは、柔軟性とレイアウトのシンプルさのバランスを取りつつ、固有のケースに合わせてアプリを最適化できるように、特別に選択されたものです。

ウィンドウ サイズクラスは、アプリで利用可能な表示領域をコンパクト中程度拡大に分類します。利用可能な幅と高さは個別に分類されるため、どの時点でも、アプリにはウィンドウ サイズクラスが 2 つ(幅用と高さ用)あります。縦スクロールが一般的であることから、通常は利用可能な幅の方が利用可能な高さより重要です。したがって、ほとんどの場合、アプリの UI には幅のウィンドウ サイズクラスの方が適しています。

図 1. 幅に基づくウィンドウ サイズクラスの表現。
図 2. 高さに基づくウィンドウ サイズクラスの表現。

上図のように、ブレークポイントを使用すると、デバイスと構成の観点からレイアウトを引き続き検討できます。各サイズクラスのブレークポイントは、典型的なデバイス シナリオの一般的なケースを表しており、ブレークポイントに基づくレイアウトの設計を検討する際の基準として有用です。

サイズクラス ブレークポイント デバイスによる表現
コンパクトな幅 600 dp 未満 縦向きのスマートフォンの 99.96%
中程度の幅 600 dp 以上 縦向きのタブレットの 93.73%

開いた内側の大型ディスプレイ(縦向き)

拡大幅 840 dp 以上 横向きのタブレットの 97.22%

開いた内側の大型ディスプレイ(横向き)

コンパクトな高さ 480 dp 未満 横向きのスマートフォンの 99.78%
中程度の高さ 480 dp 以上 横向きのタブレットの 96.56%

縦向きのスマートフォンの 97.59%

拡大高さ 900 dp 以上 縦向きのタブレットの 94.25%

サイズクラスを実機で確認することは有用ですが、ウィンドウ サイズクラスは、デバイス画面のサイズによって明確に決定されるわけではありません。ウィンドウ サイズクラスは isTablet タイプのロジック用ではありません。むしろウィンドウ サイズクラスは、アプリを実行しているデバイスの種類に関係なく、アプリで利用可能なウィンドウ サイズによって決定されます。その結果、次の 2 点が重要となります。

  • 実機で、特定のウィンドウ サイズクラスが保証されるわけではありません。アプリで利用可能な画面スペースは、さまざまな理由により、デバイスの画面サイズと異なります。モバイル デバイスでは、分割画面モードによって 2 つのアプリ間で画面を分割できます。Chrome OS では、任意にサイズ変更可能なフリーフォーム ウィンドウで Android アプリを表示できます。折りたたみ式デバイスでは、デバイスを折りたたむか広げることで、2 種類の画面サイズを個別に利用できます。

  • ウィンドウ サイズクラスは、アプリの全期間を通じて変わる可能性があります。アプリの実行中に、デバイスの向きの変更、マルチタスク、折りたたみ、展開により、利用可能な画面スペースが変化することがあります。そのため、ウィンドウ サイズクラスは動的であり、アプリの UI はそれに適応する必要があります。

ウィンドウ サイズクラスは、マテリアル デザインのレスポンシブ レイアウト グリッドのレイアウト ブレークポイントに対応します。ウィンドウ サイズクラスを使用すると、大まかなアプリ レイアウトを決定できます。たとえば、追加の画面スペースを利用するために特定の正規レイアウトを使用するかどうかを決定できます。

ビューベースのアプリでは、Jetpack WindowManager ライブラリによって提供される現在のウィンドウ指標に基づいて、ウィンドウ サイズクラスを計算する必要があります。下記のビュー(Kotlin)およびビュー(Java)のサンプルコードは、変更のたびにブレークポイントに基づいてウィンドウ サイズクラスを計算し、更新を受信する方法の例を示しています。

Compose ベースのアプリでは、material3-window-size-class ライブラリを使用し、現在のウィンドウ指標に基づいて calculateWindowSizeClass()WindowSizeClass を計算する必要があります。

ビュー

enum class WindowSizeClass { COMPACT, MEDIUM, EXPANDED }

class MainActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // ...

        // Replace with a known container that you can safely add a
        // view to where it 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. We also 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)
                computeWindowSizeClasses()
            }
        })

        computeWindowSizeClasses()
    }

    private fun computeWindowSizeClasses() {
        val metrics = WindowMetricsCalculator.getOrCreate()
            .computeCurrentWindowMetrics(this)

        val widthDp = metrics.bounds.width() /
            resources.displayMetrics.density
        val widthWindowSizeClass = when {
            widthDp < 600f -> WindowSizeClass.COMPACT
            widthDp < 840f -> WindowSizeClass.MEDIUM
            else -> WindowSizeClass.EXPANDED
        }

        val heightDp = metrics.bounds.height() /
            resources.displayMetrics.density
        val heightWindowSizeClass = when {
            heightDp < 480f -> WindowSizeClass.COMPACT
            heightDp < 900f -> WindowSizeClass.MEDIUM
            else -> WindowSizeClass.EXPANDED
        }

        // Use widthWindowSizeClass and heightWindowSizeClass.
    }
}

ビュー

public enum WindowSizeClass { COMPACT, MEDIUM, EXPANDED }

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // ...

        // Replace with a known container that you can safely add a
        // view to where it won't affect the layout and the view
        // won't be replaced.
        ViewGroup container = 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. We also 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(new View(this) {
            @Override
            protected void onConfigurationChanged(Configuration newConfig) {
                super.onConfigurationChanged(newConfig);
                computeWindowSizeClasses();
            }
        });

        computeWindowSizeClasses();
    }

    private void computeWindowSizeClasses() {
        WindowMetrics metrics = WindowMetricsCalculator.getOrCreate()
                .computeCurrentWindowMetrics(this);

        float widthDp = metrics.getBounds().width() /
                getResources().getDisplayMetrics().density;
        WindowSizeClass widthWindowSizeClass;

        if (widthDp < 600f) {
            widthWindowSizeClass = WindowSizeClass.COMPACT;
        } else if (widthDp < 840f) {
            widthWindowSizeClass = WindowSizeClass.MEDIUM;
        } else {
            widthWindowSizeClass = WindowSizeClass.EXPANDED;
        }

        float heightDp = metrics.getBounds().height() /
                getResources().getDisplayMetrics().density;
        WindowSizeClass heightWindowSizeClass;

        if (heightDp < 480f) {
            heightWindowSizeClass = WindowSizeClass.COMPACT;
        } else if (heightDp < 900f) {
            heightWindowSizeClass = WindowSizeClass.MEDIUM;
        } else {
            heightWindowSizeClass = WindowSizeClass.EXPANDED;
        }

        // Use widthWindowSizeClass and heightWindowSizeClass.
    }
}

Compose

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            val windowSizeClass = calculateWindowSizeClass(this)
            MyApp(windowSizeClass)
        }
    }
}

アプリでウィンドウ サイズクラスを確認したら、現在のウィンドウ サイズクラスに基づいてレイアウトの変更を開始できます。

ウィンドウ サイズクラスを使用してレイアウトをレスポンシブにする方法については、以下をご覧ください。

さまざまなウィンドウ サイズクラスをサポートするためのチェックリスト

レイアウトを変更する際は、すべてのウィンドウ サイズ(特にコンパクト、中程度、拡大のブレークポイント幅)でレイアウトの動作をテストします。

コンパクト画面用の既存のレイアウトがある場合は、まず拡大幅のサイズクラスに合わせてレイアウトを最適化します。これにより、追加のコンテンツまたは UI の変更のためのスペースを最大限確保できます。その後、中程度の幅のクラスに適したレイアウトを決定し、中程度の幅の画面サイズに特化したレイアウトの追加を検討します。

折りたたみ式形状のサポートや、キーボード、マウス、タッチペンによる入力のサポートなど、アプリに固有の機能を追加してユーザー エクスペリエンスを強化します。

すべてのデバイスと画面サイズで優れたアプリを実現する方法の詳細については、大画面のアプリの品質をご覧ください。

レスポンシブ デザイン

さまざまなデバイス フォーム ファクタをサポートするための第一歩は、画面サイズのバリエーションに対応するレスポンシブなレイアウトを作成することです。

ConstraintLayout

レスポンシブ レイアウトを作成するには、UI の基本レイアウトとして ConstraintLayout を使用することをおすすめします。ConstraintLayout を使用すると、レイアウト内の他のビューとの空間的な関係に従って各ビューの位置とサイズを指定できます。そして、画面サイズの変更に応じてすべてのビューを同時に移動したりサイズ変更したりできます。

ConstraintLayout でレイアウトを作成するときは、Android Studio の Layout Editor を使用すると簡単です。Layout Editor を使用すると、XML を手作業で編集することなく、新しいビューをレイアウトにドラッグし、親ビューや兄弟ビューに対する制約を適用して、ビュー プロパティを設定できます。

図 3. ConstraintLayout を表示している Android Studio の Layout Editor。

詳しくは、ConstraintLayout でレスポンシブ UI を作成するをご覧ください。

レスポンシブな幅と高さ

さまざまな画面サイズに対してレイアウトをレスポンシブにするには、ほとんどのビュー コンポーネントの幅と高さに、ハードコードされた値ではなく wrap_contentmatch_parent0dp (match constraint) を使用します。

  • wrap_content - ビューのサイズを、コンテンツがビュー内に収まるサイズに設定できるようにします。
  • match_parent - ビューを、親ビュー内で可能な限り拡大できるようにします。
  • 0dp (match constraint) - ConstraintLayout で、match_parent と同様。ビューの制約の中で、利用可能なすべてのスペースをビューで利用できるようにします。

次に例を示します。

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/lorem_ipsum" />

図 4 は、デバイスの向きに応じて画面の幅が変わったときに、TextView の幅と高さがどのように調整されるかを示しています。

図 4. レスポンシブな TextView

TextView では、利用可能なすべてのスペースを埋めるように幅が設定され(match_parent)、含まれるテキストの高さに合わせて高さが設定されます(wrap_content)。これにより、さまざまな画面サイズとテキストの量にビューが対応できるようになります。

LinearLayout を使用している場合、ビューが利用可能なスペースを比例的に埋めるように、レイアウト ウェイトに基づいて子ビューを拡大することもできます。ただし、ネストされた LinearLayout でウェイトを使用する場合、各ビューのサイズを決めるために複数のレイアウトパスが行われるため UI のパフォーマンスが低下します。

ConstraintLayout であれば、パフォーマンスに影響を与えることなく、LinearLayout で可能なほぼすべてのレイアウトを作成できるため、ネストされた LinearLayoutConstraintLayout に変換してみてください。そうすると、制約チェーンを使用してウェイト レイアウトを定義できます。

アダプティブ デザイン

アプリのレイアウトは、常にさまざまな画面サイズに対してレスポンシブである必要があります。しかし、レスポンシブ レイアウトであっても、すべてのデバイスで最適なユーザー エクスペリエンスを提供できるとは限りません。たとえば、スマートフォン用にデザインされた UI は、タブレットでは最適なエクスペリエンスを提供しません。アダプティブ デザインは、さまざまなディスプレイ サイズ向けに最適化された代替レイアウトを提供します。

リスト / 詳細 UI 用の SlidingPaneLayout

一般的に、リスト / 詳細 UI は、異なる画面サイズに応じて異なるユーザー エクスペリエンスを提供します。大画面では、通常、リストペインと詳細ペインは横並びで表示されます。リスト内のアイテムを選択すると、アイテムの情報が詳細ペインに表示されます。UI に変化はなく、2 つのペインは横並びのままです。これに対して、小さい画面では 2 つのペインは個別に表示され、各ペインはディスプレイ領域全体を占有します。リストペインのアイテムを選択すると、リストペインに代わって、選択されたアイテムの情報を示す詳細ペインが表示されます。「戻る」操作を行うと、詳細ペインに代わってリストペインが表示されます。

SlidingPaneLayout は、2 つのユーザー エクスペリエンスのどちらが現在のウィンドウ サイズに適しているかを判断するロジックを管理します。

<?xml version="1.0" encoding="utf-8"?>
<androidx.slidingpanelayout.widget.SlidingPaneLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="280dp"
        android:layout_height="match_parent"
        android:layout_gravity="start" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="300dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        app:defaultNavHost="true"
        app:navGraph="@navigation/item_navigation" />

</androidx.slidingpanelayout.widget.SlidingPaneLayout>

SlidingPaneLayout に含まれる 2 つのビューの layout_width 属性と layout_weight 属性により、SlidingPaneLayout の動作が決まります。この例では、ウィンドウが両方のビューを表示できる十分な大きさ(幅 580 dp 以上)である場合にペインが横並びに表示されます。ただし、ウィンドウの幅が 580 dp より小さい場合は、一方のペインが他方のペインの上にスライドして、個別にアプリ ウィンドウ全体に表示されます。

ウィンドウの幅が指定した最小幅の合計(580 dp)より大きい場合は、layout_weight 値を使用して 2 つのペインのサイズを比率で指定できます。この例ではウェイトが設定されていないため、リストペインの幅は常に 280 dp です。しかし詳細ペインは、ビューの layout_weight により、常に水平方向の 580 dp を超えるスペースまで表示されます。

代替レイアウト リソース

UI デザインをさまざまな画面サイズに適応させるには、リソース修飾子で識別される代替レイアウトを使用します。

図 5. 同じアプリでも画面サイズに合わせて異なるレイアウトを使用できる。

アプリのソースコードに res/layout/ ディレクトリを追加で作成することで、画面固有のアダプティブ レイアウトを提供できます。異なるレイアウトを必要とする画面構成ごとにディレクトリを作成します。次に、画面構成修飾子を layout ディレクトリ名に追加します。たとえば、利用可能な幅が 600 dp の画面の場合は layout-w600dp になります。

この構成修飾子は、アプリの UI に利用できる表示画面スペースを表します。システムは、アプリのレイアウトを選択する際、システム デコレーション(ナビゲーション バーなど)とウィンドウ構成の変更(マルチウィンドウ モードなど)を考慮します。

Android Studio(バージョン 3.0 以降)で代替レイアウトを作成する手順は次のとおりです。

  1. デフォルトのレイアウトを開いて、ツールバーで [Orientation for Preview] をクリックします。
  2. プルダウン リストで、[Create Landscape Variant] などの候補バリアントをクリックして作成するか、[Create Other] をクリックします。
  3. [Create Other] を選択すると、[Select Resource Directory] が表示されます。左側で画面の修飾子を選択し、その修飾子を [Chosen qualifiers] リストに追加します。修飾子の追加が完了したら、[OK] をクリックします(画面サイズの修飾子については、以降のセクションをご覧ください)。

新しいレイアウト ディレクトリにデフォルトのレイアウト ファイルのコピーが作成されるため、その画面バリアントのレイアウトのカスタマイズを開始できます。

最小幅の修飾子

画面サイズの最小幅の修飾子を使用すると、最小幅(密度非依存ピクセル(dp または dip)単位)の画面に対して代替レイアウトを提供できます。

画面サイズを dp 単位で表すと、ピクセル密度の違いを気にすることなく、特定の画面寸法に合わせたレイアウトを作成できます。

たとえば、スマートフォンやタブレット用に最適化された main_activity という名前のレイアウトを作成するには、さまざまなバージョンのファイルを個別のディレクトリに作成します。

res/layout/main_activity.xml           # For phones (smaller than 600dp smallest width)
res/layout-sw600dp/main_activity.xml   # For 7” tablets (600dp wide or wider)

画面の両辺の最小サイズは、デバイスのそのときの向きに関係なく最小幅の修飾子によって指定されます。したがって、レイアウトに使用できる全体的な画面サイズを指定する場合はこの方法が最も簡単です。

他の最小幅の値と、一般的な画面サイズの対応を次に示します。

  • 320 dp: 通常のスマートフォンの画面(240x320 ldpi、320x480 mdpi、480x800 hdpi など)
  • 480 dp: 5 インチ未満の大画面スマートフォン(480x800 mdpi)
  • 600 dp: 7 インチ タブレット(600x1,024 mdpi)
  • 720 dp: 10 インチ タブレット(720x1,280 mdpi、800x1,280 mdpi など)

図 6 は、画面 dp 幅と、画面サイズおよび向きが一般的にどのように対応しているかを詳しく示しています。

図 6. さまざまな画面サイズをサポートするための推奨幅ブレークポイント。

最小幅の修飾子の値は dp です。これは、システムが(生ピクセル解像度ではなく)ピクセル密度を認識した後に利用できる画面スペースの大きさが重要であるためです。

最小幅などのリソース修飾子を使用して指定するサイズは実際の画面サイズではありません。サイズは、アプリのウィンドウで利用可能な幅または高さを dp 単位で指定します。Android システムでは、システム UI(画面下部のシステムバーや画面上部のステータスバーなど)用に画面の一部が使用される場合があるため、画面の一部がレイアウト向けに使用できないことがあります。アプリがマルチウィンドウ モードで使用されている場合、アプリは、アプリを含むウィンドウのサイズにしかアクセスできません。ウィンドウのサイズが変更されると、新しいウィンドウ サイズで構成の変更がトリガーされ、適切なレイアウト ファイルが選択されるようになります。そのため、宣言するリソース修飾子のサイズは、アプリが必要とするスペースのみを指定する必要があります。レイアウト用のスペースを提供する際にシステム UI で使用するスペースが考慮されます。

利用可能な幅の修飾子

画面の最小幅に基づいてレイアウトを変更するのではなく、そのとき利用可能な幅または高さに基づいてレイアウトを変更したい場合があります。たとえば、画面の幅が 600 dp 以上のときに必ず 2 ペイン レイアウトを使用するとします。画面の幅は、デバイスが横向きか縦向きかに応じて変わる可能性があります。その場合は次のように、利用可能な幅の修飾子を使用します。

res/layout/main_activity.xml         # For phones (smaller than 600dp available width)
res/layout-w600dp/main_activity.xml  # For 7” tablets or any screen with 600dp available width
                                     # (possibly landscape phones)

アプリで利用可能な高さを重視する場合は、利用可能な高さの修飾子を使用できます。たとえば、画面の高さが 600 dp 以上の画面の場合は layout-h600dp です。

向きの修飾子

最小幅の修飾子と利用可能な幅の修飾子の組み合わせでもすべてのサイズ バリエーションに対応できるかもしれませんが、縦向きと横向きを切り替えてユーザー エクスペリエンスを変更することも可能です。

それには、port 修飾子または land 修飾子をレイアウト ディレクトリ名に追加します。ただし、向きの修飾子はサイズの修飾子の後に配置してください。次に例を示します。

res/layout/main_activity.xml                # For phones
res/layout-land/main_activity.xml           # For phones in landscape
res/layout-sw600dp/main_activity.xml        # For 7” tablets
res/layout-sw600dp-land/main_activity.xml   # For 7” tablets in landscape

画面構成に関するすべての修飾子について詳しくは、アプリリソースの概要をご覧ください。

フラグメントによる UI コンポーネントのモジュール化

複数の画面サイズに合わせてアプリを設計するときは、フラグメントを使用して UI ロジックを個別のコンポーネントに抽出し、UI の動作をアクティビティ間で不必要に重複させないようにします。大画面の場合はフラグメントを組み合わせてマルチペイン レイアウトを作成でき、小画面の場合はフラグメントを別々のアクティビティに配置できます。

たとえば、リストと詳細のパターン(前述の SlidingPaneLayout を参照)は、リストを含むフラグメントと、リストアイテムの詳細を含むフラグメントで実装できます。大画面ではフラグメントが横並びに表示され、小画面では個別に、画面全体に表示されます。

詳しくは、フラグメントの概要をご覧ください。

アクティビティの埋め込み

アプリが複数のアクティビティで構成されている場合、アクティビティの埋め込みを使用すると、アダプティブ UI を簡単に作成できます。

アクティビティの埋め込みは、複数のアクティビティ、または同じアクティビティの複数のインスタンスを、アプリのタスク ウィンドウに同時に表示します。大画面ではアクティビティが横並びに表示され、小画面では積み重ねて表示されます。

XML 構成ファイルを作成することで、アプリでのアクティビティの表示方法を決定します。システムはこのファイルを使用して、画面サイズに応じた適切な表示を決定します。または、Jetpack WindowManager API を呼び出すこともできます。

アクティビティの埋め込みはデバイスの向きの変更と、折りたたみ式デバイスをサポートします。デバイスの回転や折りたたみと展開に合わせて、アクティビティの積み重ねと積み重ね解除を行います。

詳しくは、アクティビティの埋め込みをご覧ください。

画面サイズとアスペクト比

さまざまな画面サイズとアスペクト比でアプリをテストして、UI が正しくスケーリングされることを確認します。

Android 10(API レベル 29)以降では、さまざまなアスペクト比がサポートされています。折りたたみ式のフォーム ファクタは、折りたたんだときの 21:9 などの縦長画面から、展開したときのアスペクト比 1:1 の正方形まで、さまざまです。

できるだけ多くのデバイスに対応させるには、次に示すうち、できるだけ多くの画面アスペクト比でアプリをテストします。

図 7. 画面のアスペクト比

サポートできないアスペクト比がある場合は、maxAspectRatiominAspectRatio を使用して、アプリが対処できる最大と最小のアスペクト比を指定します。この制限を超える画面では、アプリが互換モードになる可能性があります。

テストするすべての画面サイズのデバイスを用意できない場合は、Android Emulator を使用すると、ほぼすべての画面サイズをエミュレートできます。

実機でテストしたいがデバイスを持っていない場合は、Firebase Test Lab を使用すると、Google データセンターのデバイスにアクセスできます。

特定の画面サイズのサポート

アプリを特定の画面サイズでは動作しないようにする場合は、アプリのサイズ変更量を制限したり、画面構成に基づいてアプリをインストールできるデバイスを制限したりできます。詳しくは、画面サポートの制限の宣言をご覧ください。

参考情報