Android プラットフォームは、ステータスバーやナビゲーション バーなどのシステム UI を描画する役割を担います。このシステム UI は、ユーザーが使用しているアプリに関係なく表示されます。WindowInsets
はシステム UI に関する情報を提供します。これにより、アプリが正しい領域に描画され、システム UI によって UI が隠されることがなくなります。
デフォルトでは、アプリの UI は、ステータスバーやナビゲーション バーなどのシステム UI 内にレイアウトされるように制限されています。これにより、アプリのコンテンツがシステム UI 要素によって隠されることがなくなります。
ただし、システム UI も表示されるこれらの領域では、表示をオプトインすることをおすすめします。これにより、よりシームレスなユーザー エクスペリエンスが実現し、使用可能なウィンドウ スペースをアプリが最大限に活用できるようになります。また、これにより、特にソフトウェア キーボードを表示または非表示にする際、アプリもシステム UI と一緒にアニメーション化できます。
こうした領域での表示を有効にしてシステム UI の背後にコンテンツを表示することを、「エッジ ツー エッジ」と呼びます。このページでは、さまざまなタイプのインセット、エッジ ツー エッジにオプトインする方法、インセット API を使用して UI をアニメーション化し、アプリの一部を隠す方法について説明します。
インセットの基礎
アプリをエッジ ツー エッジ対応にする場合、重要なコンテンツや操作がシステム UI で隠れないようにする必要があります。たとえば、ボタンがナビゲーション バーの背後に配置されている場合、ユーザーがクリックできない可能性があります。
システム UI のサイズと配置場所の情報は、インセットで指定します。
システム UI の各部分には、サイズと配置場所を示す対応するタイプのインセットがあります。たとえば、ステータスバー インセットはステータスバーのサイズと位置を提供し、ナビゲーション バー インセットはナビゲーション バーのサイズと位置を提供します。各タイプのインセットは、上、左、右、下の 4 ピクセル寸法で構成されています。これらのディメンションは、システム UI がアプリ ウィンドウの対応する側からどの程度延びるかを指定します。そのため、そのタイプのシステム UI との重複を避けるため、その量でアプリ UI を挿入する必要があります。
これらの Android の組み込みインセットタイプは、WindowInsets
で使用できます。
ステータスバーを説明するインセット。これらは、通知アイコンやその他のインジケーターを含む上部のシステム UI バーです。 |
|
ステータスバーが表示されるタイミングをインセットします。没入型全画面モードに移行したため、現在ステータスバーが非表示になっている場合、メイン ステータスバーのインセットは空になりますが、これらのインセットは空になりません。 |
|
ナビゲーション バーを説明するインセット。デバイスの左側、右側、下部に表示されるシステム UI バーで、タスクバーやナビゲーション アイコンを説明します。これらは、ユーザーが選択したナビゲーション方法やタスクバーの操作に基づいて、実行時に変更できます。 |
|
ナビゲーション バーが表示されるタイミングをインセットします。没入型全画面モードに移行したため、現在ナビゲーション バーが非表示になっている場合、メインのナビゲーション バーのインセットは空になりますが、これらのインセットは空になりません。 |
|
上部のタイトルバーなど、フリーフォームのウィンドウ内にある場合の、システム UI ウィンドウの装飾について説明するインセット。 |
|
字幕バーが表示されるタイミングのために字幕バーがインセットされます。字幕バーが現在非表示になっている場合、メインの字幕バーのインセットは空になりますが、空になりません。 |
|
ステータスバー、ナビゲーション バー、字幕バーを含むシステムバー インセットを結合したもの。 |
|
表示される場合のシステムバーのインセット。没入型全画面モードになったためにシステムバーが現在非表示になっている場合、メインのシステムバーのインセットは空になりますが、空になりません。 |
|
ソフトウェア キーボードが占有する下部のスペースの量を表すインセット。 |
|
現在のキーボード アニメーションの前にソフトウェア キーボードが占有していたスペースの量を表すインセット。 |
|
現在のキーボード アニメーションの後にソフトウェア キーボードが占有するスペースの量を表すインセット。 |
|
ナビゲーション UI に関する詳細情報を記述するインセットの一種で、「タップ」がアプリではなくシステムによって処理されるスペースの大きさを指定します。ジェスチャー ナビゲーションを使用する透明なナビゲーション バーでは、一部のアプリ要素をシステム ナビゲーション UI でタップできます。 |
|
表示されるときのタップ可能な要素のインセット。没入型全画面モードになったため、タップ可能な要素が現在非表示になっている場合、メインのタップ可能な要素のインセットは空になりますが、これらのインセットは空になりません。 |
|
システムがナビゲーションのジェスチャーをインターセプトするインセットの数を表すインセット。アプリは |
|
常にシステムによって処理され、 |
|
ディスプレイ カットアウト(ノッチまたはピンホール)と重ならないために必要な間隔を表すインセット。 |
|
ウォーターフォール ディスプレイの曲線領域を表すインセット。ウォーターフォール ディスプレイでは、画面の端に沿って湾曲した領域がデバイスの側面に沿って広がります。 |
コンテンツが不明瞭にならないよう、次の 3 つの「安全な」インセット型がまとめられています。
次の「安全な」インセット タイプは、基盤となるプラットフォーム インセットに基づいて、さまざまな方法でコンテンツを保護します。
WindowInsets.safeDrawing
を使用して、システム UI の下に描画すべきでないコンテンツを保護します。これはインセットの最も一般的な使用方法です。システム UI によって(部分的または完全に)隠れたコンテンツを描画しないようにします。WindowInsets.safeGestures
を使用すると、ジェスチャーでコンテンツを保護できます。これにより、システム ジェスチャーがアプリのジェスチャー(ボトムシート、カルーセル、ゲーム内の操作など)と競合するのを回避できます。WindowInsets.safeDrawing
とWindowInsets.safeGestures
の組み合わせとしてWindowInsets.safeContent
を使用し、コンテンツが視覚的に重なったり、操作が重なったりしないようにします。
インセットのセットアップ
アプリがコンテンツを描画する場所を完全に制御できるようにするには、次のセットアップ手順を実施します。この手順を行わないと、システム UI の背後にアプリが黒色または単色で描画されたり、ソフトウェア キーボードと同期してアニメーション化されなくなったりする可能性があります。
Activity.onCreate
でenableEdgeToEdge()
を呼び出します。この呼び出しは、アプリがシステム UI の背後に表示されることをリクエストします。これにより、アプリでこれらのインセットを使用して UI を調整する方法を制御できます。アクティビティの
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()
があり、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 のサイズに合わせてアニメーション表示され、imePadding()
修飾子が下部パディングの適用を開始し、IME が開くと LazyColumn
のサイズを変更します。imePadding()
修飾子が下部パディングの適用を開始すると、その量のインセットも消費されるようになります。そのため、システムバーの間隔の一部として imePadding()
修飾子によってすでに適用されているので、Spacer
の高さが減少し始めます。imePadding()
修飾子によって下パディングがシステムバーよりも大きい場合、Spacer
の高さはゼロになります。
IME を閉じると、変化は逆に行われます。imePadding()
がシステムバーの下端よりも小さく適用されると、Spacer
は高さ 0 から拡張され始め、最終的に 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
や固定された高さスペーサーなど)でパディングまたはスペースが提供される場合に、子に通知する場合に便利です。
@OptIn(ExperimentalLayoutApi::class) 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") } } }
図 1. IME アニメーション
マテリアル 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
に設定します。
リソース
- Now in Android - Kotlin と Jetpack Compose のみで構築された、完全に機能する Android アプリです。
あなたへのおすすめ
- 注: JavaScript がオフになっている場合はリンクテキストが表示されます
- マテリアル コンポーネントとレイアウト
CoordinatorLayout
を Compose に移行する- その他の考慮事項