概要
Android 15 以降、edge-to-edge レイアウトが標準化されます。Compose アプリで edge-to-edge を正しく実装するには、enableEdgeToEdge() を呼ぶだけでなく、各 UI コンポーネントが ステータスバー・ナビゲーションバー・IME(入力方式エディター)のウィンドウインセットを適切に処理する必要があります。本記事では、インセット処理の責任分担パターンを提案し、実装例を交えて解説します。
背景・問題
Android 15(targetSdk 35+)では edge-to-edge が強制的に有効化されます。従来は `setDecorFitsSystemWindows(false)` を明示的に呼ぶ必要がありましたが、今後はデフォルト動作になります。
Jetpack Compose でこれに対応しようとすると、複数の問題に直面します。
- コンテンツの隠れ:インセットを処理しないと、コンテンツがステータスバーやナビゲーションバーに隠れてしまいます。
- 二重カウント:複数のコンポーネントが同じインセットを処理すると、意図しない余白が生まれます。例えば、Material3 の `Scaffold` がステータスバーのパディングを加え、さらに `TopAppBar` も処理すれば、余分な余白が発生します。
- IME との干渉:`windowSoftInputMode="adjustResize"` と `enableEdgeToEdge()` の組み合わせは、Android 11 以下で IME パディングの二重適用を招きます。
これらの問題を回避するには、各コンポーネントが責任分担してインセットを処理する設計が有効です。
解決策
1. enableEdgeToEdge() の呼び出し位置
enableEdgeToEdge() は `onCreate()` の冒頭で、super.onCreate() よりも前に呼んでください。これにより、初期フレームから edge-to-edge が有効になります。
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge() // super より前に呼ぶ
super.onCreate(savedInstanceState)
setContent { ... }
}
2. インセット処理の責任分担
edge-to-edge では、コンテンツ領域全体がシステムバー配下に拡張されます。この領域を有効に使うため、以下の責任分担パターンを採用します。
- ステータスバー:`TopAppBar` に任せる
- ナビゲーションバー:バナー広告(または固定フッター)に任せる
- IME:入力フィールドの親コンテナで `imePadding()` を使う
各コンポーネントが 1 つの責務に絞ることで、二重カウントを防ぎ、コードの意図が明確になります。
3. Scaffold の contentWindowInsets をゼロに
Material3 の `Scaffold` はデフォルトで、ステータスバー・ナビゲーションバーのインセットを `paddingValues` に含めます。各コンポーネント(TopAppBar・BannerAd 等)が個別にインセットを処理する場合は、二重カウントを防ぐため、Scaffold の `contentWindowInsets` にゼロを指定します。
Scaffold(
contentWindowInsets = WindowInsets(0, 0, 0, 0),
topBar = { TopAppBar(...) }, // windowInsets はデフォルトのまま
) { paddingValues ->
...
}
4. TopAppBar のデフォルト動作
`TopAppBar` のデフォルト `windowInsets` は `TopAppBarDefaults.windowInsets`(= `WindowInsets.statusBars`)に設定されています。これにより、TopAppBar の背景色はステータスバー領域まで拡張され、内部コンテンツはステータスバー分だけ下に押し下げられます。特に修正は不要です。
5. ナビゲーションバーはバナー広告で処理する
バナー広告がある場合、ナビゲーションバーの処理をバナーに委ねるパターンが有効です。以下の 3 つの modifier を組み合わせます。
height(AdSize.BANNER.height.dp + navigationBarHeight):バナー高さ + ナビゲーションバー高さbackground(MaterialTheme.colorScheme.surfaceVariant):ナビゲーションバー領域の背景色を設定navigationBarsPadding():AdView 自体の描画をナビゲーションバーの上に限定
6. NavigationBar の二重カウント回避
Material3 の `NavigationBar` はデフォルトでナビゲーションバーのインセットを内部パディングに追加します。バナー広告が既にナビゲーションバーを処理している場合は、`NavigationBar` に `windowInsets = WindowInsets(0)` を指定して、二重カウントを防ぎます。
7. IME は imePadding() のみで制御
`windowSoftInputMode="adjustResize"` と `enableEdgeToEdge()` の組み合わせは、古い Android バージョンで IME パディングの二重適用を招きます。
AndroidManifest.xml から `windowSoftInputMode="adjustResize"` を削除し、Compose 側では入力フィールドの親コンテナで `imePadding()` のみを使用してください。
Surface(modifier = modifier.fillMaxSize()) {
Column(
modifier = Modifier
.fillMaxSize()
.imePadding() // IME 表示時に下部パディングを追加
) {
TextField(...)
LazyColumn(modifier = Modifier.weight(1f)) { ... }
}
}
LazyColumn に `weight(1f)` を指定することで、IME 表示時に Column の縮小分が LazyColumn に集中し、フォーカス中のテキストフィールドがキーボードに隠れません。
まとめ
Jetpack Compose で edge-to-edge を正しく実装するには、以下の 4 つのポイントが重要です。
enableEdgeToEdge()を `super.onCreate()` より前に呼ぶ- Scaffold の `contentWindowInsets` にゼロを指定し、各コンポーネントが個別にインセットを処理する
- ナビゲーションバーの処理をバナー広告(または固定フッター)に委ねることで、NavigationBar の `windowInsets` をゼロに設定できる
- IME は `imePadding()` のみで制御し、`windowSoftInputMode="adjustResize"` を削除する
これらのパターンを採用することで、インセット処理の意図が明確になり、コードが簡潔で保守しやすくなります。手順チェッカーアプリで実装された例を参考にしながら、自身のアプリに適用してみてください。
0 件のコメント:
コメントを投稿