Android

[Android] Modifier.consumeWindowInsetsとModifier.windowInsetsPaddingの動作まとめ

Katz

はじめに

※本記事ではEdge-to-edgeの説明には記載せず、理解している前提で解説を進めます。

targetSdkVersion36(Android 16)からはEdge-to-edgeがデフォルトで有効となり、そのためAndroidアプリ開発者はtargetSdkVersion36にアップデートする際にはEdge-to-edgeの対応が必要になります。

あわせて読みたい
エッジ ツー エッジのオプトアウトの廃止
エッジ ツー エッジのオプトアウトの廃止

公式ドキュメントでは以下の3つのModifier修飾子をを用いてEdge-to-edge対応を進めるという記載があります。そのためAndroid開発者この3つのModifier修飾子を利用してEdge-to-edge対応を進めることになります。

  • Modifer.windowInsetsPaddingとよばれるパディング修飾子でインセットを消費する。かつ消費したインセットの領域に重ならないようにパディングを追加する
  • Modifier.navigationBarsPaddingなどを代表とするパディング修飾子のヘルパーでインセットを消費する。かつ消費してインセットの領域に重ならないようにパディングを追加する
  • Modifier.consumeWindowInsetsと呼ばれるModifier修飾子でインセットを消費する。このModifier修飾子ではインセットの領域に重ならないようにパディングを追加するかは選択できる。
あわせて読みたい
Android Developer | アプリでコンテンツをエッジ ツー エッジで表示し、Compose でウィンドウ インセットを処理する
Android Developer | アプリでコンテンツをエッジ ツー エッジで表示し、Compose でウィンドウ インセットを処理する

ですがModifier修飾子について文面だけ見ても動作が理解しにくいところがあったので、どのような仕様・定義・動作をするものなのか調べてみました。

Modifer.windowInsetsPaddingとは?

仕様

  • インセット領域にコンテンツが入り込まないようにパディングを追加します。
  • 他のインセット系パディング修飾子や、親レイアウトで consumeWindowInsets が使用されている場合、それらによってすでに消費されたインセットは、この修飾子では除外されます。
  • また、指定されたインセットは子レイアウトに対しても消費されるため、子で再度同じインセットが適用されることはありません。

定義

/**
 * Adds padding so that the content doesn't enter [insets] space.
 *
 * Any insets consumed by other insets padding modifiers or [consumeWindowInsets] on a parent
 * layout will be excluded from [insets]. [insets] will be [consumed][consumeWindowInsets] for
 * child layouts as well.
 *
 * For example, if an ancestor uses [statusBarsPadding] and this modifier uses
 * [WindowInsets.Companion.systemBars], the portion of the system bars that the status bars uses
 * will not be padded again by this modifier.
 *
 * @sample androidx.compose.foundation.layout.samples.insetsPaddingSample
 * @see WindowInsets
 */
@Stable
fun Modifier.windowInsetsPadding(insets: WindowInsets): Modifier = composed(
    debugInspectorInfo {
        name = "windowInsetsPadding"
        properties["insets"] = insets
    }
) {
    remember(insets) { InsetsPaddingModifier(insets) }
}

動作確認

  • 1つ目のBoxでModifier.windowInsetsPaddingを呼び出しWindowInsets.statusBarsとWindowInsets.navigationBarsのインセットを消費して、そのインセットに対応するPaddingを追加している。
  • 2つ目のBoxでもModifier.windowInsetsPaddingを呼び出しWindowInsets.statusBarsとWindowInsets.navigationBarsのインセットを消費して、そのインセットに対応するPaddingを追加しようとしている。しかしすでに指定したインセットは消費済みであるためPaddingは追加されない。
  • このようにModifier.windowInsetsPaddingでは親のレイアウトで消費したインセットを子のレイアウトが把握して、余分なインセットのためのPaddingが追加されない仕組みになっている。
@Composable
fun InsetsModifierNestScreen(onClose: () -> Unit) {
    BackHandler {
        onClose()
    }

    Box(
        modifier =
            Modifier
                .fillMaxSize()
                .background(Color.Red.copy(alpha = 0.5f))
                .windowInsetsPadding(WindowInsets.statusBars)
                .windowInsetsPadding(WindowInsets.navigationBars),
    ) {
        Box(
            modifier =
                Modifier
                    .fillMaxSize()
                    .background(Color.Blue.copy(alpha = 0.5f))
                    .windowInsetsPadding(WindowInsets.statusBars)
                    .windowInsetsPadding(WindowInsets.navigationBars),
        ) {
            Box(
                modifier =
                    Modifier
                        .fillMaxSize()
                        .background(Color.Green.copy(alpha = 0.5f)),
            )
        }
    }
}

Modifier.navigationBarsPaddingとは?

仕様

  • ナビゲーションバーのインセットに対応するようにパディングを追加します。
  • 他のインセット系パディング修飾子や、親レイアウトで consumeWindowInsets が使用されている場合、それによってすでに消費されたインセットは、この修飾子によるパディングから除外されます。
  • WindowInsets.Companion.navigationBars は子レイアウトに対しても消費されるため、子で再度同じインセットが適用されることはありません。

定義

  • Modifier.navigationBarsPaddingは実はwindowInsetsPaddingを内部的に呼び出している。
  • そのためModifier.navigationBarsPaddingとModifier.windowInsetsPadding(WindowInsets.navigationBars)と動作が同じになる。
/**
 * Adds padding to accommodate the [navigation bars][WindowInsets.Companion.navigationBars] insets.
 *
 * Any insets consumed by other insets padding modifiers or [consumeWindowInsets] on a parent layout
 * will be excluded from the padding. [WindowInsets.Companion.navigationBars] will be
 * [consumed][consumeWindowInsets] for child layouts as well.
 *
 * For example, if a parent layout uses [systemBarsPadding], the
 * area that the parent layout pads for the status bars will not be padded again by this
 * [navigationBarsPadding] modifier.
 *
 * When used, the [WindowInsets][android.view.WindowInsets] will be consumed.
 *
 * @sample androidx.compose.foundation.layout.samples.statusBarsAndNavigationBarsPaddingSample
 */
fun Modifier.navigationBarsPadding() =
    windowInsetsPadding(debugInspectorInfo { name = "navigationBarsPadding" }) { navigationBars }

動作確認

  • 1つ目のBoxでModifier.statusBarsPaddingとModifier.navigationBarsPaddingを呼び出し、WindowInsets.statusBarsとWindowInsets.navigationBarsのインセットを消費して、そのインセットに対応するPaddingを追加している。
  • 2つ目のBoxでModifier.statusBarsPaddingとModifier.navigationBarsPaddingを呼び出し、WindowInsets.statusBarsとWindowInsets.navigationBarsのインセットを消費して、そのインセットに対応するPaddingを追加しようとしている。しかしすでに指定したインセットは消費済みであるためPaddingは追加されない。
  • このようにModifier.statusBarsPaddingとModifier.navigationBarsPaddingでもModifier.windowInsetsPaddingと同じように親のレイアウトで消費したインセットを子のレイアウトが把握して、余分なインセットのためのPaddingが追加されない仕組みになっている。
@Composable
fun InsetsFuncNestScreen(onClose: () -> Unit) {
    BackHandler {
        onClose()
    }

    Box(
        modifier =
            Modifier
                .fillMaxSize()
                .background(Color.Red.copy(alpha = 0.5f))
                .statusBarsPadding()
                .navigationBarsPadding(),
    ) {
        Box(
            modifier =
                Modifier
                    .fillMaxSize()
                    .background(Color.Blue.copy(alpha = 0.5f))
                    .statusBarsPadding()
                    .navigationBarsPadding(),
        ) {
            Box(
                modifier =
                    Modifier
                        .fillMaxSize()
                        .background(Color.Green.copy(alpha = 0.5f)),
            )
        }
    }
}

Modifier.consumeWindowInsetsPaddingとは?

仕様

  • windowInsetsPadding に似ていますが、パディングを追加せずに、他のインセット修飾子によってまだ消費されていないインセットを消費します。

定義

/**
 * Consume insets that haven't been consumed yet by other insets Modifiers similar to
 * [windowInsetsPadding] without adding any padding.
 *
 * This can be useful when content offsets are provided by [WindowInsets.asPaddingValues].
 * This should be used further down the hierarchy than the [PaddingValues] is used so
 * that the values aren't consumed before the padding is added.
 *
 * @sample androidx.compose.foundation.layout.samples.consumedInsetsSample
 */
@Stable
fun Modifier.consumeWindowInsets(insets: WindowInsets): Modifier = composed(
    debugInspectorInfo {
        name = "consumeWindowInsets"
        properties["insets"] = insets
    }
) {
    remember(insets) { UnionInsetsConsumingModifier(insets) }
}

動作確認

  • 1つ目のBoxでModifier.consumeWindowInsetsを呼び出し、WindowInsets.statusBarsとWindowInsets.navigationBarsのインセットに対するPaddingを追加せずインセットを消費したことにする。
  • 2つ目のBoxでもModifier.windowInsetsPaddingを呼び出しWindowInsets.statusBarsとWindowInsets.navigationBarsのインセットを消費して、そのインセットに対応するPaddingを追加しようとしている。しかしすでに指定したインセットは消費済みであるためPaddingは追加されない。
  • このようにModifier.consumeWindowInsetsを呼び出すことで、Paddingを追加せずに特定のインセットを消費したことにできる。
    • 使われるのは稀そうだが、子レイアウトが呼び出すModifier.windowInsetsPaddingによるPadding追加を無視したいケース、または既に親レイアウトがModifier.paddingでインセットに対応するPadddingが追加されてしまっているケースなどの対処で利用ができそう。
@Composable
fun ConsumeScreen(onClose: () -> Unit) {
    BackHandler {
        onClose()
    }

    Box(
        modifier =
            Modifier
                .fillMaxSize()
                .background(Color.Red.copy(alpha = 0.5f))
                .consumeWindowInsets(WindowInsets.statusBars)
                .consumeWindowInsets(WindowInsets.navigationBars),
    ) {
        Box(
            modifier =
                Modifier
                    .fillMaxSize()
                    .background(Color.Blue.copy(alpha = 0.5f))
                    .windowInsetsPadding(WindowInsets.statusBars)
                    .windowInsetsPadding(WindowInsets.navigationBars),
        ) {
            Box(
                modifier =
                    Modifier
                        .fillMaxSize()
                        .background(Color.Green.copy(alpha = 0.5f)),
            )
        }
    }
}

まとめ

  • Modifier.windowInsetsPaddingではどのインセットが消費されたか管理するためModifier.windowInsetsPaddingを複数回呼び出されてしまっても余分なPaddingを確保することを防ぐことができる。
  • Modifier.navigationBarsPaddingを代表とするヘルパー関数は内部でModifier.windowInsetsPaddingを呼び出しているので、同じくインセットが消費されたか管理している。そのため基本動作が同じである。
  • Modifier.windowInsetsPaddingではインセットを消費するのと同時にインセットに対応するPaddingを追加する、Modifier.consumeWindowInsetsを利用すればインセットを消費して、インセットに対応するPaddingを追加しないということもできる。

参考資料

あわせて読みたい
Android Developer | アプリでコンテンツをエッジ ツー エッジで表示し、Compose でウィンドウ インセットを処理する
Android Developer | アプリでコンテンツをエッジ ツー エッジで表示し、Compose でウィンドウ インセットを処理する
あわせて読みたい
WindowInsetsのConsumeについて
WindowInsetsのConsumeについて
あわせて読みたい
GitHub | Kaleidot725 AndroidEdgeToEdge サンプルコード
GitHub | Kaleidot725 AndroidEdgeToEdge サンプルコード
ABOUT ME
Katz(Yusuke Katsuragawa)
Katz(Yusuke Katsuragawa)
Androidエンジニア
AndroidエンジニアをやっているKatzです。最近はKotlin Multiplatformを中心にやっています。経歴やお仕事の依頼については、私のプロフィールに詳細を記載していますので、ご確認ください。
記事URLをコピーしました