Androidアプリを開発・運用していると、こんな警告がGoogle Play Consoleに届いていませんか?
「2025年8月31日までにtargetSdkVersionを35以上に更新してください」
対応しないとアップデートが配信できなくなるわけですが、targetSdkVersion 35への引き上げで厄介なのが Edge-to-Edge(エッジ・トゥ・エッジ)の強制適用 です。何も考えずにバージョンを上げると「ステータスバーにコンテンツが被って読めない」「ナビゲーションバーにボタンが隠れて押せない」といったレイアウト崩れが容赦なく発生します。
しかもAndroid 16ではオプトアウト手段すら廃止されるため、実質的に逃げ道がなくなります。
この記事ではEdge-to-Edgeの概要からAndroid 15・16での対応方針、よくあるハマりどころまでまとめています。
この記事でわかること
- Edge-to-Edgeとは何か(基本的な概念)
- Android 15(targetSdk 35)とAndroid 16(targetSdk 36)での変更内容の違い
- Google Playのtarget API level要件と期限
- 対応方針の選び方(View・Compose別)
- オプトアウトの一時しのぎと、その限界
- よくあるレイアウト崩れのパターンと解決の糸口
Edge-to-Edgeとは
Edge-to-Edgeとは、アプリのコンテンツをステータスバーやナビゲーションバー(いわゆるシステムバー)の裏側まで描画領域を広げる表示方式です。
これまでのAndroidアプリは「システムバーの外側」に表示領域が収まる仕様でした。Edge-to-Edgeが有効になると、コンテンツがシステムバーの背後にまで広がり、見た目はスッキリするのですが対応を入れていないと大事なUIがシステムバーに隠れてしまいます。
【従来】
┌───────────────────┐ ← ステータスバー(アプリ領域外)
├───────────────────┤
│ │
│ アプリコンテンツ │
│ │
├───────────────────┤
└───────────────────┘ ← ナビゲーションバー(アプリ領域外)
【Edge-to-Edge有効後】
┌───────────────────┐
│ ステータスバー │ ← コンテンツが裏に描画される
│ │
│ アプリコンテンツ │
│ │
│ ナビゲーションバー │ ← コンテンツが裏に描画される
└───────────────────┘
適切な対応(Insetsを使ったpadding制御)を入れることで、コンテンツがシステムバーに重ならないようにレイアウトを調整できます。
Android 15・16での変更点と対応期限
Android 15(targetSdk 35)の変更
2025年8月31日以降、Google PlayへのアプリリリースにはtargetSdk 35以上が必要です。
targetSdk 35をAndroid 15以降のデバイスで実行すると、Edge-to-Edgeが強制適用されます。ただしAndroid 15の段階では、windowOptOutEdgeToEdgeEnforcement 属性を true に設定することでオプトアウト(無効化)が可能です。
AndroidManifest.xml
xml
<application
android:windowOptOutEdgeToEdgeEnforcement="true"
...>
</application>
これを設定すると、Android 15端末でもEdge-to-Edgeが適用されない従来の挙動を維持できます。
ただしこれはあくまで 一時しのぎ です。
Android 16(targetSdk 36)の変更
Android 16(targetSdk 36)では、windowOptOutEdgeToEdgeEnforcement によるオプトアウトが廃止されます。
つまり、targetSdk 36を設定したアプリはEdge-to-Edgeを回避できなくなります。現時点でのtargetSdk 36の要件適用時期は公式発表待ちですが、毎年8月末に引き上げられるパターンを踏まえると、2026年8月31日ごろに要求される可能性が高いと考えておくのが無難です。
OSバージョンごとの挙動をまとめると以下のとおりです。
| targetSdk | Android 14以下 | Android 15 | Android 16以上 |
|---|---|---|---|
| 34以下 | Edge-to-Edge無効 | Edge-to-Edge無効 | Edge-to-Edge強制 |
| 35 | Edge-to-Edge無効 | Edge-to-Edge強制(オプトアウト可) | Edge-to-Edge強制 |
| 36 | Edge-to-Edge無効 | Edge-to-Edge強制 | Edge-to-Edge強制(オプトアウト不可) |
注意: Android 16以上の端末では、targetSdkのバージョンに関わらずEdge-to-Edgeが適用されるケースがあります。公式ドキュメントを必ずご確認ください。 動作の変更点: Android 16 以上をターゲットとするアプリ | Android Developers
対応方針の選び方
短期対応(Android 15のみ)
時間がないときの応急処置として windowOptOutEdgeToEdgeEnforcement="true" でオプトアウトする方法があります。手順は前述の通りです。ただしAndroid 16ではこの手段が使えないため、根本対応を先送りにしているだけという認識は持っておく必要があります。
長期対応(推奨):全バージョンでEdge-to-Edgeを正式有効化する
全OSバージョンで意図的にEdge-to-Edgeを有効化し、WindowInsetsを使ってpaddingを制御するアプローチが推奨されます。Android 16での強制適用に備えつつOSバージョン間の挙動を統一できるため保守性が高くなります。
Viewベースの場合
Step 1: enableEdgeToEdge() をActivityで呼び出す
MainActivity.kt
kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge() // ← これを追加
setContentView(R.layout.activity_main)
}
Step 2: ViewCompat.setOnApplyWindowInsetsListener でInsetsを取得してpaddingを設定する
kotlin
ViewCompat.setOnApplyWindowInsetsListener(binding.rootLayout) { v, insets ->
val sysBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
val ime = insets.getInsets(WindowInsetsCompat.Type.ime())
val bottomPadding = maxOf(sysBars.bottom, ime.bottom)
v.setPadding(sysBars.left, sysBars.top, sysBars.right, bottomPadding)
insets
}
Type.systemBars(): ステータスバーとナビゲーションバーのインセットを取得します。Type.ime(): キーボード(ソフトウェアキーボード)のインセットを取得します。maxOf(sysBars.bottom, ime.bottom): キーボード表示時にナビゲーションバーより背が高い場合を考慮してpaddingを切り替えています。
Jetpack Composeの場合
Composeで androidx.compose.material3 の Scaffold を使っている場合、Material3が自動でWindowInsetsを処理してくれるため追加対応は最小限で済みます。
kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MyAppTheme {
Scaffold(
modifier = Modifier.fillMaxSize()
) { paddingValues ->
// paddingValuesをコンテンツに渡す
MainContent(modifier = Modifier.padding(paddingValues))
}
}
}
}
ただし子画面(Child Screen)で別途 Scaffold を使っている場合、Insetsが二重に適用されて余白が広くなりすぎることがあります。この場合は子側のWindowInsetsを WindowInsets(0, 0, 0, 0) で打ち消す対応が必要になります。
kotlin
@Composable
fun ChildScreen() {
Scaffold(
topBar = {
TopAppBar(
title = { Text("画面タイトル") },
windowInsets = WindowInsets(0, 0, 0, 0) // ← ここで打ち消す
)
},
contentWindowInsets = WindowInsets(0, 0, 0, 0) // ← ここも
) { paddingValues ->
LazyColumn(contentPadding = paddingValues) { ... }
}
}
よくあるレイアウト崩れのパターン
ステータスバーにタイトルや文字が被る
ステータスバー分の paddingTop が取れていないケースです。Type.statusBars() のインセットを取得してpaddingに反映させます。
ナビゲーションバーにボタンが隠れて押せない
ナビゲーションバー分の paddingBottom が不足しているケースです。BottomNavigationやFABなど画面下部の操作要素に特に注意が必要です。
キーボード表示時にコンテンツが隠れる
Type.ime() のインセットを考慮していないと、キーボード表示時にスクロールできずコンテンツが見えなくなります。前述の maxOf(sysBars.bottom, ime.bottom) で対応できます。
画面上部に謎の余白が生まれる
Composableのネスト構造でInsetsが二重適用されているケースです。Scaffold + 子Scaffoldの組み合わせで起きやすいです。
Q&A
まとめ
- Android 15(targetSdk 35)からEdge-to-Edgeが強制適用されるよ。
- 2025年8月31日以降はtargetSdk 35以上でないとGoogle Playにリリースできないよ。
- Android 15では
windowOptOutEdgeToEdgeEnforcement="true"でオプトアウトできるけど、Android 16では廃止されるよ。 - 長期的にはWindowInsetsを使ったpaddingの制御で全OSバージョンの挙動を統一するのが推奨アプローチだよ。
- Jetpack Compose(Material3)はScaffoldが自動でInsetsを処理してくれるけど、子画面での二重適用には注意してね。