ビューでコンテンツをエッジ ツー エッジで表示する

Compose をお試しください
Jetpack Compose は、Android で推奨される UI ツールキットです。Compose でエッジ ツー エッジを扱う方法について説明します。

Android 15 以降を搭載したデバイスで SDK 35 以降をターゲットにすると、アプリはエッジ ツー エッジで表示されます。ウィンドウは、システムバーの背後に描画することで、ディスプレイの幅と高さ全体に広がります。システムバーには、ステータスバー、キャプション バー、ナビゲーション バーが含まれます。

多くのアプリにはトップ アプリバーがあります。トップ アプリバーは画面の上端まで伸び、ステータスバーの背後に表示されます。必要に応じて、コンテンツがスクロールすると、トップ アプリバーはステータスバーの高さまで縮小できます。

多くのアプリには、ボトム アプリバーまたはボトム ナビゲーション バーもあります。これらのバーも画面の下端まで伸び、ナビゲーション バーの背後に表示されます。それ以外の場合、アプリはナビゲーション バーの背後にスクロール コンテンツを表示する必要があります。

図 1. エッジ ツー エッジ レイアウトのシステムバー。

アプリにエッジ ツー エッジ レイアウトを実装する際は、次の点に留意してください。

  1. エッジ ツー エッジ表示を有効にする
  2. さまざまな端末タイプでユーザー エクスペリエンス を最適化するために、アダプティブ レイアウトを実装する
  3. 視覚的な重複を処理する
  4. システムバーの背後にスクリムを表示することを検討する
ステータスバーの背後に画像を表示する例
図 2.ステータスバーの背後の画像の例。

エッジ ツー エッジ表示を有効にする

アプリが SDK 35 以降をターゲットにしている場合、Android 15 以降のデバイスではエッジ ツー エッジが自動的に有効になります。

以前の Android バージョンでエッジ ツー エッジを有効にするには、 enableEdgeToEdgeonCreateActivity で手動で呼び出します。

Kotlin

 override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         WindowCompat.enableEdgeToEdge(window)
        ...
      }

Java

 @Override
      protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        WindowCompat.enableEdgeToEdge(getWindow());
        ...
      }

デフォルトでは、enableEdgeToEdge() を呼び出すと、3 ボタン ナビゲーション モードを除き、システムバーが透明になります。3 ボタン ナビゲーション モードでは、ナビゲーション バーに半透明のスクリムが適用されます。システム アイコンとスクリムの色は、システムのライトテーマまたはダークテーマに基づいて調整されます。

アプリでエッジ ツー エッジ表示を有効にする方法については、 enableEdgeToEdge() 関数を使用せずに エッジ ツー エッジ表示を手動で設定するをご覧ください。

インセットを使用して重複を処理する

図 3 に示すように、アプリのビューの一部がシステムバーの背後に描画されることがあります。

重複を解消するには、インセットに対応します。インセットは、画面のどの部分がシステム UI(ナビゲーション バーやステータスバーなど)と交差するかを指定します。交差とは、コンテンツの上に表示されることを意味する場合もありますが、システム ジェスチャーをアプリに通知することもできます。

アプリをエッジ ツー エッジで表示する場合に適用されるインセットのタイプは次のとおりです。

  • システムバー インセット: タップ可能で、システムバーによって視覚的に隠れてはならないビューに最適です。

  • ディスプレイ カットアウト インセット: デバイスの形状により画面にカットアウトが生じる可能性がある領域用です。

  • システム ジェスチャー インセット: システムが使用するジェスチャー ナビゲーション領域用です。アプリよりも優先されます。

システムバー インセット

システムバー インセットは、最もよく使用されるインセットです。これは、システム UI がアプリの Z 軸上で表示される領域を表します。タップ可能で、システムバーによって視覚的に隠れてはならないアプリのビューを移動またはパディングする場合に最適です。

たとえば、図 3 のフローティング アクション ボタン(FAB)は、ナビゲーション バーによって部分的に 隠されています。

エッジ ツー エッジが実装されているが、ナビゲーション バーが FAB を覆っている例
図 3.エッジ ツー エッジ レイアウトで FAB と重複するナビゲーション バー。

ジェスチャー モードまたはボタンモードでこのような視覚的な重複を回避するには、ビューのマージンを増やすことができます。 getInsets(int) には WindowInsetsCompat.Type.systemBars()を使用します。

次のコード例は、システムバー インセットを実装する方法を示しています。

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(fab) { v, windowInsets ->
  val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
  // Apply the insets as a margin to the view. This solution sets
  // only the bottom, left, and right dimensions, but you can apply whichever
  // insets are appropriate to your layout. You can also update the view padding
  // if that's more appropriate.
  v.updateLayoutParams<MarginLayoutParams> {
      leftMargin = insets.left
      bottomMargin = insets.bottom
      rightMargin = insets.right
  }

  // Return CONSUMED if you don't want the window insets to keep passing
  // down to descendant views.
  WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(fab, (v, windowInsets) -> {
  Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
  // Apply the insets as a margin to the view. This solution sets only the
  // bottom, left, and right dimensions, but you can apply whichever insets are
  // appropriate to your layout. You can also update the view padding if that's
  // more appropriate.
  MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
  mlp.leftMargin = insets.left;
  mlp.bottomMargin = insets.bottom;
  mlp.rightMargin = insets.right;
  v.setLayoutParams(mlp);

  // Return CONSUMED if you don't want the window insets to keep passing
  // down to descendant views.
    return WindowInsetsCompat.CONSUMED;
});

このソリューションを図 3 に示す例に適用すると、図 4 に示すように、ボタンモードで視覚的な重複が発生しなくなります。

半透明のナビゲーション バーが FAB を覆っていない
図 4.ボタンモードでの視覚的な重複を解消する。

図 5 に示すように、ジェスチャー ナビゲーション モードにも同じことが当てはまります。

ジェスチャー ナビゲーションによるエッジ ツー エッジ
図 5.ジェスチャー ナビゲーション モードでの視覚的な重複を解消する。

ディスプレイ カットアウト インセット

一部のデバイスにはディスプレイ カットアウトがあります。通常、カットアウトは画面の上部にあり、ステータスバーに含まれています。デバイスの画面が横向きモードの場合、カットアウトは垂直方向の端に表示されることがあります。アプリが画面に表示するコンテンツに応じて、ディスプレイ カットアウトを回避するためにパディングを実装する必要があります。デフォルトでは、アプリはディスプレイ カットアウトに描画されるためです。

たとえば、多くのアプリ画面にはアイテムのリストが表示されます。ディスプレイ カットアウトやシステムバーでリストアイテムを隠さないでください。

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(binding.recyclerView) { v, insets ->
  val bars = insets.getInsets(
    WindowInsetsCompat.Type.systemBars()
      or WindowInsetsCompat.Type.displayCutout()
  )
  v.updatePadding(
    left = bars.left,
    top = bars.top,
    right = bars.right,
    bottom = bars.bottom,
  )
  WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(mBinding.recyclerView, (v, insets) -> {
  Insets bars = insets.getInsets(
    WindowInsetsCompat.Type.systemBars()
    | WindowInsetsCompat.Type.displayCutout()
  );
  v.setPadding(bars.left, bars.top, bars.right, bars.bottom);
  return WindowInsetsCompat.CONSUMED;
});

WindowInsetsCompat の値を決定するには、システムバーとディスプレイ カットアウトのタイプを論理和演算します。

RecyclerViewclipToPadding を設定して、パディングがリストアイテムとともにスクロールするようにします。これにより、次の例に示すように、ユーザーがスクロールしたときにアイテムがシステムバーの背後に移動します。

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

システム ジェスチャー インセット

システム ジェスチャー インセットは、システム ジェスチャーがアプリよりも優先されるウィンドウの領域を表します。これらの領域は、図 6 ではオレンジ色で示されています。

システム ジェスチャー インセットの例
図 6.システム ジェスチャー インセット。

システムバー インセットと同様に、システム ジェスチャー インセットの重複を回避できます using getInsets(int) with WindowInsetsCompat.Type.systemGestures()

このインセットを使用して、スワイプ可能なビューを端から離れた位置に移動またはパディングします。一般的なユース ケースとしては、ボトムシート、 ゲームでのスワイプ、および ViewPager2を使用して実装されたカルーセルなどがあります。

Android 10 以降では、システム ジェスチャー インセットには、ホーム ジェスチャー用の下部のインセットと、戻るジェスチャー用の左右のインセットが含まれています。

システム ジェスチャー インセットの測定値の例
図 7.システム ジェスチャー インセットの測定値。

次のコード例は、システム ジェスチャー インセットを実装する方法を示しています。

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
    val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
    // Apply the insets as padding to the view. Here, set all the dimensions
    // as appropriate to your layout. You can also update the view's margin if
    // more appropriate.
    view.updatePadding(insets.left, insets.top, insets.right, insets.bottom)

    // Return CONSUMED if you don't want the window insets to keep passing down
    // to descendant views.
    WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
    Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures());
    // Apply the insets as padding to the view. Here, set all the dimensions
    // as appropriate to your layout. You can also update the view's margin if
    // more appropriate.
    view.setPadding(insets.left, insets.top, insets.right, insets.bottom);

    // Return CONSUMED if you don't want the window insets to keep passing down
    // to descendant views.
    return WindowInsetsCompat.CONSUMED;
});

マテリアル コンポーネント

多くのビューベースの Android マテリアル コンポーネント (com.google.android.material)は、インセットを自動的に処理します。これには、 BottomAppBarBottomNavigationViewNavigationRailViewNavigationViewが含まれます。

ただし、AppBarLayout はインセットを自動的に処理しません。上部のインセットを処理するには、 android:fitsSystemWindows="true" を追加します。

Compose で Material Components in Compose を使用してインセットを処理する方法をご覧ください。

下位互換性のあるインセットのディスパッチ

インセットが子ビューにディスパッチされないようにして、パディングの過剰な適用を回避するには、インセットを 使用して WindowInsetsCompat.CONSUMED 定数を使用します。ただし、Android 10(API レベル 29 以前)を搭載したデバイスでは、WindowInsetsCompat.CONSUMED を呼び出した後にインセットが兄弟にディスパッチされないため、意図しない視覚的な重複が発生する可能性があります。

壊れたインセット ディスパッチの例
図 8.インセットのディスパッチが失敗した例。 ViewGroup 1 が Android 10(API レベル 29)以前でインセットを使用すると、インセットが兄弟ビューにディスパッチされず、TextView 2 がシステムのナビゲーション バーと重複します。ただし、Android 11(API レベル 30)以降では、インセットは想定どおりに兄弟ビューにディスパッチされます。

サポートされているすべての Android バージョンでインセットが兄弟にディスパッチされることを確認するには、インセットを使用する前に ViewGroupCompat#installCompatInsetsDispatch を使用します。これは、 AndroidX Core および Core-ktx 1.16.0-alpha01 以降で利用できます。

Kotlin

// Use the i.d. assigned to your layout's root view, e.g. R.id.main
val rootView = findViewById(R.id.main)
// Call before consuming insets
ViewGroupCompat.installCompatInsetsDispatch(rootView)

Java

// Use the i.d. assigned to your layout's root view, e.g. R.id.main
LinearLayout rootView = findViewById(R.id.main);
// Call before consuming insets
ViewGroupCompat.installCompatInsetsDispatch(rootView);
固定インセットのディスパッチの例
図 9.ViewGroupCompat#installCompatInsetsDispatch を呼び出した後のインセットのディスパッチを修正しました。

没入モード

一部のコンテンツは全画面表示で表示すると、ユーザーの没入感を高めることができます。 WindowInsetsController ライブラリと WindowInsetsControllerCompat ライブラリを使用すると、没入モードのシステムバーを非表示にできます。

Kotlin

val windowInsetsController =
      WindowCompat.getInsetsController(window, window.decorView)

// Hide the system bars.
windowInsetsController.hide(Type.systemBars())

// Show the system bars.
windowInsetsController.show(Type.systemBars())

Java

Window window = getWindow();
WindowInsetsControllerCompat windowInsetsController =
      WindowCompat.getInsetsController(window, window.getDecorView());
if (windowInsetsController == null) {
    return;
  }
// Hide the system bars.
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars());

// Show the system bars.
windowInsetsController.show(WindowInsetsCompat.Type.systemBars());

この機能の実装について詳しくは、没入モードのシステムバーを非表示にする をご覧ください。

システムバー アイコン

enableEdgeToEdge を呼び出すと、デバイスのテーマが変更されたときにシステムバー アイコンの色が更新されます。

エッジ ツー エッジに移行する際に、アプリの背景とコントラストがつくように、システムバー アイコンの色を手動で更新する必要がある場合があります。たとえば、明るいステータスバー アイコンを作成するには、次のようにします。

Kotlin

WindowCompat.getInsetsController(window, window.decorView)
    .isAppearanceLightStatusBars = false

Java

WindowCompat.getInsetsController(window, window.getDecorView())
    .setAppearanceLightStatusBars(false);

システムバーの保護

アプリが SDK 35 以降をターゲットにすると、エッジ ツー エッジが適用されます。 システムのステータスバーとジェスチャー ナビゲーション バーは透明ですが、3 ボタン ナビゲーション バーは半透明です。enableEdgeToEdge を呼び出して、下位互換性を持たせます。

ただし、システムのデフォルトはすべてのユースケースで機能するとは限りません。透明なシステムバーと半透明のシステムバーのどちらを使用するかを判断するには、 Android システムバーのデザイン ガイドラインエッジ ツー エッジのデザインをご覧ください。

透明なシステムバーを作成する

Android 15(SDK 35)以降をターゲットにするか、以前のバージョンのデフォルト引数で enableEdgeToEdge() を呼び出して、透明なステータスバーを作成します。

Android 15 以降をターゲットにするか、以前のバージョンのデフォルト引数で enableEdgeToEdge() を呼び出して、透明なジェスチャー ナビゲーション バーを作成します。3 ボタン ナビゲーション バーの場合は、Window.setNavigationBarContrastEnforcedfalse に設定します。そうしないと、半透明のスクリムが適用されます。

半透明のシステムバーを作成する

半透明のステータスバーを作成するには、次の操作を行います。

  1. androidx-core 依存関係を 1.16.0-beta01 以降に更新します。
  2. XML レイアウトを androidx.core.view.insets.ProtectionLayout でラップし、ID を割り当てます。
  3. プログラムで ProtectionLayout にアクセスして保護を設定し、ステータスバーのサイドと GradientProtection を指定します。

<androidx.core.view.insets.ProtectionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/list_protection"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ScrollView
        android:id="@+id/item_list"
        android:clipToPadding="false"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!--items-->

    </ScrollView>

</androidx.core.view.insets.ProtectionLayout>

findViewById<ProtectionLayout>(R.id.list_protection)
    .setProtections(
        listOf(
            GradientProtection(
                WindowInsetsCompat.Side.TOP,
                // Ideally, this is the pane's background color
                paneBackgroundColor
            )
        )
    )

GradientProtection に渡される ColorInt がコンテンツの背景と一致していることを確認します。たとえば、折りたたみ式デバイスに表示されるリストと詳細のレイアウトでは、リストパネルと詳細パネルで異なる色の GradientProtections が使用されることがあります。

図 1. 異なる色のグラデーション保護。

半透明のジェスチャー ナビゲーション バーは作成しないでください。半透明の 3 ボタン ナビゲーション バーを作成するには、次のいずれかを行います。

  • レイアウトがすでに ProtectionView でラップされている場合は、追加の ColorProtection または GradientProtectionsetProtections メソッドに渡すことができます。その前に、window.isNavigationBarContrastEnforced = false であることを確認してください。
  • それ以外の場合は、window.isNavigationBarContrastEnforced = true を設定します。

その他のヒント

インセットを処理する際のその他のヒント。

スクロール コンテンツをエッジ ツー エッジにする

インセットを処理して clipToPaddingfalse に設定することで、RecyclerView または NestedScrollView の最後のリストアイテムがシステムバーで隠れていないことを確認します。

次の動画は、エッジ ツー エッジ表示が無効(左)と有効(右)の RecyclerView を示しています。

コードサンプルについては、 RecyclerView で動的リストを作成する のコード スニペットをご覧ください。

全画面表示のダイアログをエッジ ツー エッジにする

全画面表示のダイアログをエッジ ツー エッジにするには、ダイアログで enableEdgeToEdge を呼び出します。

Kotlin

class MyAlertDialogFragment : DialogFragment() {
    override fun onStart(){
        super.onStart()
        dialog?.window?.let { WindowCompat.enableEdgeToEdge(it) }
    }
    ...
}

Java

public class MyAlertDialogFragment extends DialogFragment {
    @Override
    public void onStart() {
        super.onStart();
        Dialog dialog = getDialog();
        if (dialog != null) {
            Window window = dialog.getWindow();
            if (window != null) {
                WindowCompat.enableEdgeToEdge(window);
            }
        }
    }
    ...
}

参考情報

エッジ ツー エッジへの移行について詳しくは、以下のリファレンスをご覧ください。

ブログ

デザイン

その他のドキュメント

動画