WindowInsetsRulers について

WindowInsets は、システム UI によって部分的にまたは完全に隠される画面領域を処理するための Jetpack Compose の標準 API です。ステータスバー、ナビゲーション バー、画面キーボードなどが含まれます。代わりに、事前定義された WindowInsetsRulersSafeDrawing など)を Modifier.fitInside または Modifier.fitOutside に渡して、コンテンツをシステムバーやディスプレイ カットアウトに合わせたり、カスタム WindowInsetsRulers を作成したりすることもできます。

WindowInsetsRulers のメリット

  • 消費の複雑さを回避: レイアウトの配置フェーズで動作します。つまり、インセット消費チェーンを完全にバイパスし、親レイアウトが何をしたかに関係なく、システムバーとディスプレイ カットアウトの正しい絶対位置を常に提供できます。Modifier.fitInside メソッドまたは Modifier.fitOutside メソッドを使用すると、祖先 Composable がインセットを誤って消費している場合の問題を修正するのに役立ちます。
  • システムバーを簡単に回避: アプリのコンテンツがシステムバーやディスプレイ カットアウトを回避するのに役立ちます。WindowInsets を直接使用するよりも簡単です。
  • 高度なカスタマイズ性: デベロッパーは、コンテンツをカスタム ルーラーに合わせたり、カスタム レイアウトでレイアウトを正確に制御したりできます。

WindowInsetsRulers のデメリット

  • 測定には使用できません: 配置フェーズで動作するため、提供される位置情報は、それ以前の測定フェーズでは使用できません
  • レイアウトの不安定性の可能性: 親レイアウトのサイズが子レイアウトのサイズに依存している場合、クラッシュが発生する可能性があります。WindowInsetsRulers を使用する子要素は配置中に位置やサイズが変更される可能性があるため、不安定なレイアウト サイクルが発生する可能性があります。

カスタム WindowInsetsRulers を作成する

コンテンツをカスタム ルーラーに沿って配置できます。たとえば、親コンポーザブルがインセットを適切に処理せず、ダウンストリームの子でパディングの問題が発生するユースケースについて考えてみましょう。この問題は、Modifier.fitInside を使用するなど、他の方法でも解決できますが、次の例と動画に示すように、親アップストリームで問題を修正することなく、子コンポーザブルを正確に配置するカスタム ルーラーを作成することもできます。

@Composable
fun WindowInsetsRulersDemo(modifier: Modifier) {
    Box(
        contentAlignment = BottomCenter,
        modifier = modifier
            .fillMaxSize()
            // The mistake that causes issues downstream, as .padding doesn't consume insets.
            // While it's correct to instead use .windowInsetsPadding(WindowInsets.navigationBars),
            // assume it's difficult to identify this issue to see how WindowInsetsRulers can help.
            .padding(WindowInsets.navigationBars.asPaddingValues())
    ) {
        TextField(
            value = "Demo IME Insets",
            onValueChange = {},
            modifier = modifier
                // Use alignToSafeDrawing() instead of .imePadding() to precisely place this child
                // Composable without having to fix the parent upstream.
                .alignToSafeDrawing()

            // .imePadding()
            // .fillMaxWidth()
        )
    }
}

fun Modifier.alignToSafeDrawing(): Modifier {
    return layout { measurable, constraints ->
        if (constraints.hasBoundedWidth && constraints.hasBoundedHeight) {
            val placeable = measurable.measure(constraints)
            val width = placeable.width
            val height = placeable.height
            layout(width, height) {
                val bottom = WindowInsetsRulers.SafeDrawing.current.bottom
                    .current(0f).roundToInt() - height
                val right = WindowInsetsRulers.SafeDrawing.current.right
                    .current(0f).roundToInt()
                val left = WindowInsetsRulers.SafeDrawing.current.left
                    .current(0f).roundToInt()
                measurable.measure(Constraints.fixed(right - left, height))
                    .place(left, bottom)
            }
        } else {
            val placeable = measurable.measure(constraints)
            layout(placeable.width, placeable.height) {
                placeable.place(0, 0)
            }
        }
    }
}

次の動画は、左側の画像でアップストリームの親が原因で発生した IME インセットの消費に関する問題の例と、右側の画像でカスタム ルーラーを使用して問題を解決する方法を示しています。TextField コンポーザブルの下に余分なパディングが表示されています。これは、ナビゲーション バーのパディングが親によって消費されなかったためです。前述のコードサンプルで示されているように、カスタム ルーラーを使用して、右側の画像内の正しい位置に子を配置します。

親が制約されていることを確認する

WindowInsetsRulers を安全に使用するには、親が有効な制約を提供していることを確認してください。親はサイズが定義されている必要があり、WindowInsetsRulers を使用する子のサイズに依存することはできません。親コンポーザブルで fillMaxSize または他のサイズ変更子を使用します。

同様に、WindowInsetsRulers を使用するコンポーザブルを verticalScroll などのスクロール コンテナ内に配置すると、スクロール コンテナが上限のない高さの制約を提供するため、ルーラーのロジックと互換性がなく、予期しない動作が発生する可能性があります。