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 つの「安全な」インセット タイプで要約されています。
これらの「安全な」インセット タイプは、基盤となるプラットフォーム インセットに基づいて、さまざまな方法でコンテンツを保護します。
- システム UI の下に描画されてはならないコンテンツを保護するには、
WindowInsets.safeDrawing
を使用します。インセットの最も一般的な使用方法は、システム UI によって(部分的または全体的に)隠されたコンテンツが描画されないようにすることです。 WindowInsets.safeGestures
を使用すると、ジェスチャーでコンテンツを保護できます。これにより、システム ジェスチャーとアプリのジェスチャー(ボトムシート、カルーセル、ゲーム内のジェスチャーなど)との競合を回避できます。WindowInsets.safeContent
をWindowInsets.safeDrawing
とWindowInsets.safeGestures
と組み合わせて、コンテンツに視覚的な重なりや操作の重複がないようにします。
インセットの設定
アプリがコンテンツを描画する場所を完全に制御できるようにするには、次のセットアップ手順を行います。この手順を行わないと、アプリでシステム 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()
はそのようなメソッドの 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
の高さはゼロになります。
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 に移行する- その他の考慮事項