アプリでコンテンツをエッジ ツー エッジで表示する

Compose の方法を試す
Jetpack Compose は、Android に推奨される UI ツールキットです。Compose でエッジ ツー エッジを扱う方法について学びます。

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

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

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

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

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

  1. エッジ ツー エッジ表示を有効にする
  2. 視覚的な重複を処理します。
  3. システムバーの背後にスクリムを表示することを検討してください。
ステータスバーの背後に表示される画像の例
図 2. ステータスバーの背後に表示される画像の例。

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

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

以前のバージョンの Android でエッジツーエッジを有効にするには、次の操作を行います。

  1. アプリまたはモジュールの build.gradle ファイルで、androidx.activity ライブラリへの依存関係を追加します。

    Kotlin

    dependencies {
        val activity_version = activity_version
        // Java language implementation
        implementation("androidx.activity:activity:$activity_version")
        // Kotlin
        implementation("androidx.activity:activity-ktx:$activity_version")
    }
    

    Groovy

    dependencies {
        def activity_version = activity_version
        // Java language implementation
        implementation 'androidx.activity:activity:$activity_version'
        // Kotlin
        implementation 'androidx.activity:activity-ktx:$activity_version'
    }
    
  2. enableEdgeToEdge 拡張関数をアプリにインポートします。

ActivityonCreateenableEdgeToEdge を呼び出して、エッジ ツー エッジを手動で有効にします。setContentView の前に呼び出す必要があります。

Kotlin

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

Java

     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
       EdgeToEdge.enable(this);
       super.onCreate(savedInstanceState);
       ...
     }
   

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

enableEdgeToEdge() 関数は、アプリを端から端まで配置する必要があることを自動的に宣言し、システムバーの色を調整します。

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

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

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

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

アプリを端から端まで表示する場合に適用されるインセットのタイプは次のとおりです。

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

  • ディスプレイ カットアウトの切り抜き: デバイスの形状により画面の切り抜きがある可能性がある領域。

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

システムバーのインセット

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

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

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

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

次のコード例は、システムバーの切り欠きを実装する方法を示しています。

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 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 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;
});

システムバーとディスプレイ カットアウト タイプの論理 OR を取得して、WindowInsetsCompat の値を決定します。

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

<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. システム ジェスチャー インセット。

システムバーの切り欠きと同様に、getInsets(int)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;
});

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

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

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

Compose のマテリアル コンポーネントでインセットを処理する方法をご覧ください。

没入モード

一部のコンテンツは、全画面表示で視聴することで、より没入感のあるエクスペリエンスを提供できます。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());

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

システムバーの保護

アプリが SDK 35 以降をターゲットとしている場合、エッジツーエッジが適用されます。システム ステータスバーとジェスチャー ナビゲーション バーは透明ですが、3 ボタン ナビゲーション バーは半透明です。

デフォルトの半透明の 3 ボタン ナビゲーションの背景保護を削除するには、Window.setNavigationBarContrastEnforcedfalse に設定します。

参考情報

WindowInsets、ジェスチャー ナビゲーション、インセットの仕組みについて詳しくは、次のリファレンスをご覧ください。