ステータスバーやナビゲーション バーなどのシステム UI の描画は、Android プラットフォームが担当します。このシステム UI は、ユーザーが使用しているアプリに関係なく表示されます。
WindowInsets
は、アプリが正しい領域に描画され、UI がシステム UI で隠れないように、システム UI に関する情報を提供します。
Android 14(API レベル 34)以前では、アプリの UI はデフォルトでシステムバーとディスプレイ カットアウトの下に描画されません。
Android 15(API レベル 35)以降では、アプリが SDK 35 をターゲットとすると、アプリはシステム バーの下に描画され、切り欠きが表示されます。これにより、よりシームレスなユーザー エクスペリエンスが実現し、アプリは利用可能なウィンドウ領域を最大限に活用できます。
システム UI の背後にコンテンツを表示することを、エッジ ツー エッジと呼びます。このページでは、さまざまな種類のインセット、エッジツーエッジにする方法、インセット API を使用して UI をアニメーション化し、アプリのコンテンツがシステム UI 要素に隠れないようにする方法について説明します。
インセットの基本
アプリがエッジ ツー エッジ表示になる場合は、重要なコンテンツや操作がシステム UI で隠れないようにする必要があります。たとえば、ボタンがナビゲーション バーの背後に配置されている場合、ユーザーがクリックできない可能性があります。
システム UI のサイズと配置場所に関する情報は、インセットで指定します。
システム UI の各部分には、サイズと配置場所を表す対応するタイプのインセットがあります。たとえば、ステータスバーのインセットはステータスバーのサイズと位置を指定しますが、ナビゲーション バーのインセットはナビゲーション バーのサイズと位置を指定します。各タイプのインセットは、上、左、右、下の 4 つのピクセル寸法で構成されます。これらのディメンションでは、システム UI がアプリのウィンドウの対応する側からどの程度延びるかを指定します。そのため、このようなシステム UI と重ならないように、アプリ UI をその分だけ内側に配置する必要があります。
次の組み込みの Android インセット タイプは、WindowInsets
で使用できます。
ステータスバーを説明するインセット。通知アイコンやその他のインジケーターを含む、上部のシステム UI バーです。 |
|
ステータスバーは、表示されているときにインセットされます。ステータスバーが現在非表示になっている場合(没入感のある全画面モードに入ったため)、メインのステータスバーの切り欠きは空になりますが、これらの切り欠きは空ではありません。 |
|
ナビゲーション バーを説明するインセット。デバイスの左側、右側、または下部にあるシステム UI バーで、タスクバーやナビゲーション アイコンを記述します。これらは、ユーザーが好むナビゲーション方法やタスクバーの操作に基づいて、実行時に変更される場合があります。 |
|
ナビゲーション バーは、表示されているときにインセットされます。ナビゲーション バーが現在非表示になっている場合(没入型全画面モードに入ったため)、メイン ナビゲーション バーのインセットは空になりますが、これらのインセットは空ではありません。 |
|
フリーフォーム ウィンドウの場合、上部のタイトルバーなど、システム UI ウィンドウの装飾を説明するインセット。 |
|
字幕バーは、表示されているときにインセットされます。字幕バーが現在非表示になっている場合、メインの字幕バーの切り欠きは空になりますが、これらの切り欠きは空ではありません。 |
|
ステータスバー、ナビゲーション バー、キャプション バーなどのシステムバーのインセットの統合。 |
|
システムバーは、表示されているときにインセットされます。システムバーが現在非表示になっている場合(没入型全画面モードに入ったため)、メインのシステムバーの切り欠きは空になりますが、これらの切り欠きは空ではありません。 |
|
ソフトウェア キーボードが占有する下部のスペースの量を示すインセット。 |
|
現在のキーボード アニメーションの前にソフトウェア キーボードが占有していたスペースの量を示すインセット。 |
|
現在のキーボード アニメーションの後にソフトウェア キーボードが占有するスペースの量を示すインセット。 |
|
ナビゲーション UI の詳細情報を記述するインセットの一種。アプリではなくシステムによって「タップ」が処理されるスペースの量を指定します。ジェスチャー ナビゲーションを使用する透明なナビゲーション バーの場合、一部のアプリ要素はシステム ナビゲーション UI からタップできます。 |
|
タップ可能な要素は、表示されているときにインセットされます。タップ可能な要素が現在非表示になっている場合(没入型全画面モードに入ったため)、メインのタップ可能な要素のインセットは空になりますが、これらのインセットは空ではありません。 |
|
システムがナビゲーションのジェスチャーをインターセプトするインセットの量を表すインセット。アプリは、 |
|
システム ジェスチャーのサブセット。常にシステムによって処理され、 |
|
ディスプレイの切り欠き(ノッチまたはピンホール)と重ならないようにするために必要なスペースの量を表すインセット。 |
|
ウォーターフォール ディスプレイの湾曲した領域を表すインセット。ウォーターフォール ディスプレイは、画面の端にカーブした領域があり、画面がデバイスの側面を巻き込むように表示されます。 |
これらのタイプは、コンテンツが隠れないようにする 3 つの「安全な」インセット タイプにまとめられます。
これらの「安全な」インセット タイプは、基盤となるプラットフォームのインセットに基づいて、さまざまな方法でコンテンツを保護します。
- システム UI の下に描画されないコンテンツを保護するには、
WindowInsets.safeDrawing
を使用します。これは、システム UI によって隠れているコンテンツ(部分的または完全に)を描画しないようにするための、インセットの最も一般的な用途です。 WindowInsets.safeGestures
を使用して、ジェスチャーでコンテンツを保護します。これにより、システム ジェスチャーがアプリのジェスチャー(ボトムシート、カルーセル、ゲームなど)と競合するのを防ぐことができます。WindowInsets.safeDrawing
とWindowInsets.safeGestures
の組み合わせとしてWindowInsets.safeContent
を使用して、コンテンツの視覚的な重複やジェスチャーの重複がないようにします。
インセットの設定
アプリでコンテンツの描画位置を完全に制御できるようにするには、次の設定手順を行います。これらの手順を実施しないと、アプリがシステム UI の背後に黒色または単色を描画したり、ソフトウェア キーボードと同期してアニメーション化されなかったりする可能性があります。
- Android 15 以降でエッジツーエッジを適用するには、ターゲット SDK 35 以降にする必要があります。アプリはシステム UI の背後に表示されます。インセットを処理することで、アプリの UI を調整できます。
- 必要に応じて、
Activity.onCreate()
でenableEdgeToEdge()
を呼び出します。これにより、以前の Android バージョンでアプリをエッジ ツー エッジにできます。 アクティビティの
AndroidManifest.xml
エントリにandroid:windowSoftInputMode="adjustResize"
を設定します。この設定により、アプリはソフトウェア IME のサイズをインセットとして受け取ることができます。これにより、アプリで IME が表示されたり消えたりしても、コンテンツを適切にパディングしてレイアウトできます。<!-- in your AndroidManifest.xml file: --> <activity android:name=".ui.MainActivity" android:label="@string/app_name" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.MyApplication" android:exported="true">
Compose API
アクティビティがすべてのインセットの処理を制御できるようになったら、Compose API を使用して、コンテンツが隠れないようにし、操作可能な要素がシステム UI と重ならないようにします。また、これらの API は、アプリのレイアウトをインセットの変更と同期します。
たとえば、アプリ全体のコンテンツにインセットを適用する最も基本的な方法は次のとおりです。
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { Box(Modifier.safeDrawingPadding()) { // the rest of the app } } }
このスニペットは、safeDrawing
ウィンドウ インセットをアプリのコンテンツ全体の周囲にパディングとして適用します。これにより、操作可能な要素がシステム UI と重複することがなくなりますが、アプリのいずれもシステム UI の背後に描画されなくなるため、エッジツーエッジ効果が実現されます。ウィンドウ全体を最大限に活用するには、画面ごとに、またはコンポーネントごとに、インセットを適用する場所を微調整する必要があります。
これらのインセット タイプはすべて、API 21 にバックポートされた IME アニメーションで自動的にアニメーション化されます。これらのインセットを使用するすべてのレイアウトも、インセット値が変更されると自動的にアニメーション化されます。
これらのインセット タイプを使用してコンポーザブル レイアウトを調整するには、パディング修飾子とインセット サイズ修飾子の 2 つの主な方法があります。
パディング修飾子
Modifier.windowInsetsPadding(windowInsets: WindowInsets)
は、指定されたウィンドウのインセットをパディングとして適用します。これは Modifier.padding
と同じように動作します。たとえば、Modifier.windowInsetsPadding(WindowInsets.safeDrawing)
は、安全な描画インセットを 4 つの側面すべてにパディングとして適用します。
最も一般的なインセット タイプ用の組み込みユーティリティ メソッドもいくつかあります。Modifier.safeDrawingPadding()
はそのようなメソッドの 1 つで、Modifier.windowInsetsPadding(WindowInsets.safeDrawing)
と同等です。他のインセット タイプにも同様の修飾子があります。
インセット サイズの修飾子
次の修飾子は、コンポーネントのサイズをインセットのサイズに設定することで、ウィンドウの切り欠きの量を適用します。
windowInsets の開始側を幅として適用します( |
|
windowInsets の端側を幅として適用します( |
|
windowInsets の上側を高さとして適用します( |
|
|
windowInsets の下側を高さとして適用します( |
これらの修飾子は、インセットのスペースを占有する Spacer
のサイズ設定に特に便利です。
LazyColumn( Modifier.imePadding() ) { // Other content item { Spacer( Modifier.windowInsetsBottomHeight( WindowInsets.systemBars ) ) } }
インセットの使用量
インセット パディング修飾子(windowInsetsPadding
と safeDrawingPadding
などのヘルパー)は、パディングとして適用されるインセットの部分を自動的に消費します。コンポジション ツリーを深く掘り下げると、ネストされたインセット パディング修飾子とインセット サイズ修飾子は、インセットの一部が外側のインセット パディング修飾子によってすでに使用されていることを認識し、インセットの同じ部分を複数回使用して余分なスペースを増やさないようにします。
インセットサイズ修飾子を使用すると、インセットがすでに使用されている場合でも、インセットの同じ部分を複数回使用することがなくなります。ただし、サイズを直接変更するため、インセット自体は消費しません。
そのため、ネストされたパディング修飾子により、各コンポーザブルに適用されるパディングの量が自動的に変更されます。
前と同じ LazyColumn
の例を見てみましょう。LazyColumn
は imePadding
修飾子によってサイズ変更されています。LazyColumn
内の最後の項目は、システムバーの下部と同じ高さに設定されます。
LazyColumn( Modifier.imePadding() ) { // Other content item { Spacer( Modifier.windowInsetsBottomHeight( WindowInsets.systemBars ) ) } }
IME が閉じている場合、IME には高さがないため、imePadding()
修飾子はパディングを適用しません。imePadding()
修飾子ではパディングが適用されないため、インセットは使用されず、Spacer
の高さはシステムバーの下側のサイズになります。
IME が開くと、IME のサイズに合わせて IME インセットがアニメーション化され、IME が開くにつれて imePadding()
修飾子が下部パディングを適用して LazyColumn
のサイズを変更します。imePadding()
修飾子が下部パディングの適用を開始すると、その量のインセットも消費され始めます。そのため、システムバーの間隔の一部は imePadding()
修飾子によってすでに適用されているため、Spacer
の高さが低下し始めます。imePadding()
修飾子によってシステムバーよりも大きい下部パディングが適用されると、Spacer
の高さは 0 になります。
IME が閉じると、変更は逆になります。imePadding()
がシステムバーの下部よりも小さく適用されると、Spacer
は高さがゼロから拡大し始め、IME が完全にアニメーション化されると、Spacer
はシステムバーの下部の高さに一致します。
この動作は、すべての windowInsetsPadding
修飾子間の通信によって実現され、他のいくつかの方法で影響を受けます。
Modifier.consumeWindowInsets(insets: WindowInsets)
も Modifier.windowInsetsPadding
と同様にインセットを使用しますが、使用したインセットをパディングとして適用しません。これは、インセットサイズ修飾子と組み合わせて、一定量のインセットがすでに使用されていることを兄弟に示す場合に便利です。
Column(Modifier.verticalScroll(rememberScrollState())) { Spacer(Modifier.windowInsetsTopHeight(WindowInsets.systemBars)) Column( Modifier.consumeWindowInsets( WindowInsets.systemBars.only(WindowInsetsSides.Vertical) ) ) { // content Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime)) } Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars)) }
Modifier.consumeWindowInsets(paddingValues: PaddingValues)
は、WindowInsets
引数を使用するバージョンと非常によく似ていますが、任意の PaddingValues
を消費します。これは、通常の Modifier.padding
や固定高のスペーサーなど、インセット パディング修飾子以外のメカニズムによってパディングまたは間隔が提供されている場合に、子に通知する場合に便利です。
Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) { // content Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime)) }
消費せずに元のウィンドウ インセットが必要な場合は、WindowInsets
値を直接使用するか、WindowInsets.asPaddingValues()
を使用して、消費の影響を受けないインセットの PaddingValues
を返します。ただし、以下の注意事項があるため、可能な限り、ウィンドウ インセットのパディング修飾子とウィンドウ インセットのサイズ修飾子を使用することをおすすめします。
インセットと Jetpack Compose のフェーズ
Compose は、基盤となる AndroidX コア API を使用してインセットを更新し、アニメーション化します。このインセットは、インセットを管理する基盤となるプラットフォーム API を使用します。このようなプラットフォームの動作により、インセットは Jetpack Compose のフェーズと特別な関係があります。
インセットの値は、コンポジション フェーズの後、レイアウト フェーズの前に更新されます。つまり、コンポジションでインセットの値を読み取る場合、通常は 1 フレーム遅れたインセットの値が使用されます。このページで説明する組み込み修飾子は、インセットの値の使用をレイアウト フェーズまで遅らせるように構築されています。これにより、インセット値が更新されたフレームと同じフレームで使用されるようになります。
WindowInsets
を使用したキーボード IME アニメーション
スクロール コンテナに Modifier.imeNestedScroll()
を適用すると、コンテナの一番下までスクロールしたときに IME が自動的に開閉されます。
class WindowInsetsExampleActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) setContent { MaterialTheme { MyScreen() } } } } @OptIn(ExperimentalLayoutApi::class) @Composable fun MyScreen() { Box { LazyColumn( modifier = Modifier .fillMaxSize() // fill the entire window .imePadding() // padding for the bottom for the IME .imeNestedScroll(), // scroll IME at the bottom content = { } ) FloatingActionButton( modifier = Modifier .align(Alignment.BottomEnd) .padding(16.dp) // normal 16dp of padding for FABs .navigationBarsPadding() // padding for navigation bar .imePadding(), // padding for when IME appears onClick = { } ) { Icon(imageVector = Icons.Filled.Add, contentDescription = "Add") } } }
マテリアル 3 コンポーネントの切り抜きサポート
使いやすさを考慮して、マテリアル 3 の組み込みコンポーザブルの多く(androidx.compose.material3
)は、マテリアルの仕様に従ってコンポーザブルがアプリに配置される方法に基づいて、インセットを処理します。
インセット処理のコンポーザブル
以下に、インセットを自動的に処理するマテリアル コンポーネントの一覧を示します。
アプリバー
TopAppBar
/SmallTopAppBar
/CenterAlignedTopAppBar
/MediumTopAppBar
/LargeTopAppBar
: システムバーの上部と横方向をパディングとして適用します。これは、ウィンドウの上部で使用されるためです。BottomAppBar
: システムバーの下部と水平側をパディングとして適用します。
コンテンツ コンテナ
ModalDrawerSheet
/DismissibleDrawerSheet
/PermanentDrawerSheet
(モーダル ナビゲーション ドロワー内のコンテンツ): コンテンツに垂直方向と開始方向のインセットを適用します。ModalBottomSheet
: 下インセットを適用します。NavigationBar
: 下と横のインセットを適用します。NavigationRail
: 垂直と開始の切り込みを適用します。
Scaffold
デフォルトでは、Scaffold
はパラメータ paddingValues
としてインセットを提供し、使用できるようにします。Scaffold
はコンテンツにインセットを適用しません。この責任はユーザーにあります。たとえば、Scaffold
内の LazyColumn
でこれらのインセットを使用するには、次のようにします。
Scaffold { innerPadding -> // innerPadding contains inset information for you to use and apply LazyColumn( // consume insets as scaffold doesn't do it by default modifier = Modifier.consumeWindowInsets(innerPadding), contentPadding = innerPadding ) { items(count = 100) { Box( Modifier .fillMaxWidth() .height(50.dp) .background(colors[it % colors.size]) ) } } }
デフォルトのインセットをオーバーライドする
コンポーザブルに渡される windowInsets
パラメータを変更して、コンポーザブルの動作を構成できます。このパラメータは、代わりに適用する別のタイプのウィンドウ インセットにすることも、空のインスタンス(WindowInsets(0, 0, 0, 0)
)を渡して無効にすることもできます。
たとえば、LargeTopAppBar
でのインセット処理を無効にするには、windowInsets
パラメータを空のインスタンスに設定します。
LargeTopAppBar( windowInsets = WindowInsets(0, 0, 0, 0), title = { Text("Hi") } )
ビューシステムの切り欠きとの相互運用
画面に同じ階層に View コードと Compose コードの両方がある場合は、デフォルトのインセットをオーバーライドする必要があります。この場合、どちらがインセットを使用するか、どちらがインセットを無視するかを明示的に指定する必要があります。
たとえば、最外側のレイアウトが Android View レイアウトの場合は、View システムでインセットを使用し、Compose では無視する必要があります。または、最外側のレイアウトがコンポーザブルの場合は、Compose でインセットを使用し、それに応じて AndroidView
コンポーザブルをパディングする必要があります。
デフォルトでは、各 ComposeView
は WindowInsetsCompat
レベルの使用量ですべてのインセットを使用します。このデフォルトの動作を変更するには、ComposeView.consumeWindowInsets
を false
に設定します。
システムバーの保護
アプリが SDK 35 以降をターゲットとしている場合、エッジツーエッジが適用されます。システム ステータスバーとジェスチャー ナビゲーション バーは透明ですが、3 ボタン ナビゲーション バーは半透明です。
デフォルトの半透明の 3 ボタン ナビゲーションの背景保護を削除するには、Window.setNavigationBarContrastEnforced
を false
に設定します。
リソース
- Android システムバー、システムバーの設計ガイダンス
- Now in Android - Kotlin と Jetpack Compose のみで構築された、完全に機能する Android アプリ。
- Android 15 でのエッジ ツー エッジの適用の処理 - Android 15 でのエッジ ツー エッジの適用について説明する Codelab
- Android 15 のエッジ ツー エッジの適用に関するインセット処理のヒント
- アプリのエッジ ツー エッジ UI をプレビューしてテストする
- Android アプリのエクスペリエンスを改善する 3 つの方法: エッジ ツー エッジ、予測型「戻る」、Glance - Android 15 のエッジ ツー エッジの適用について説明する YouTube 動画
- エッジツーエッジとインセット | Compose のヒント - インセットを処理してエッジツーエッジに描画する方法を紹介する YouTube 動画
あなたへのおすすめ
- 注: JavaScript がオフになっている場合はリンクテキストが表示されます
- マテリアル コンポーネントとレイアウト
CoordinatorLayout
を Compose に移行する- その他の考慮事項