Skip to content

Android 15以降のステータスバー領域保持

Android 15で導入されたenableEdgeToEdge()は画面を端まで使えるようにしますが、ステータスバーのスペースが無視され、アプリのコンテンツがステータスバーと重なる問題が発生します。ここではenableEdgeToEdge()使用中にステータスバーの高さを保持し、透過バーの下にコンテンツを表示する最適な手法を解説します。

🔍 問題の本質

enableEdgeToEdge()適用後、システムは以下の変化をもたらします:

  1. ステータスバーの透過化が自動で有効になる
  2. コンテンツがステータスバー領域に重なり表示される
  3. 従来のfitsSystemWindows="true"windowTranslucentStatusが効果を失う

👉 結果として通知アイコンとアプリのヘッダーが重なり、UIの操作性が低下します。

✅ 推奨解決策: WindowInsetsで動的にパディング調整

Android公式に準拠した方法です。全バージョン対応が可能で、システムバーの変化にも柔軟に対応できます。

kotlin
override fun onCreate(savedInstanceState: Bundle?) {
    enableEdgeToEdge() // エッジツーエージェ有効化
    setContentView(R.layout.activity_main)

    // ルートビューの取得
    val root: ViewGroup = findViewById(android.R.id.content)
    ViewCompat.setOnApplyWindowInsetsListener(root) { view, insets ->
        val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
        // 全子ビューにステータスバーの高さを反映
        view.children.forEach { child ->
            child.updatePadding(top = systemBars.top)
        }
        WindowInsetsCompat.CONSUMED // インセット消費を宣言
    }
}

特徴と長所

  • 👏 動的対応: 画面回転やシステムバーの表示変化を自動検知
  • ⚡ 軽量処理: WindowInsetsCompat.CONSUMEDで重複実行防止
  • 🗂 汎用性: Activity/Fragment/Compose全てで適用可
  • 📱 バージョン互換: Android 5.0 (API 21) 以降をサポート

実装ポイント

  1. ルートビュー(android.R.id.content)でリスナー設定
  2. systemBars.topでステータスバーの高さ取得
  3. updatePadding()でコンテンツ領域を調整
  4. 必須: WindowInsetsCompat.CONSUMEDでイベント消費を通知

注意

特定の子ビューのみ調整したい場合:

kotlin
ViewCompat.setOnApplyWindowInsetsListener(binding.header) { view, insets ->
    val statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top
    view.updatePadding(top = statusBarHeight)
    WindowInsetsCompat.CONSUMED
}

⚠ 非推奨な回避策(一時的対応のみ)

windowOptOutEdgeToEdgeEnforcementを使うと強制的に無効化できますが、将来の互換性は保証されません

xml
<style name="AppTheme" parent="Theme.Material3.DayNight">
    <item name="windowOptOutEdgeToEdgeEnforcement">true</item>
</style>

❗ この手法の問題点

  • Android 15以降限定の属性(旧OSでクラッシュの危険)
  • 公式が「一時的措置(temporary opt-out)」と明記
  • システムのUI改善機能を完全無効化

重要

次の方法は Android 14以前のみ有効 で、Android 15以降では機能しません:

xml
<!-- 動作しない非推奨手法 -->
<item name="android:windowTranslucentStatus">false</item>
<item name="android:fitsSystemWindows">true</item>

設計時のベストプラクティス

  1. 常に Jetpack Core の WindowInsetsCompat を使用
  2. ステータスバーの背景色設定
    xml
    <item name="android:statusBarColor">@android:color/transparent</item>
  3. ライト/ダークモード対応
    kt
    WindowCompat.getInsetsController(window, window.decorView).apply {
        isAppearanceLightStatusBars = !isDarkMode
    }

よくある質問

Q: フラグメント単位で制御できますか?
Fragment#getView()でルートビュー取得後、同様にリスナーを設定可能

Q: ナビゲーションバーのスペースも保持するには?
systemBars.bottomを取得し、同様にパディングを設定

kt
view.updatePadding(bottom = systemBars.bottom)

Q: Composeでの実装方法は?
WindowInsets APIとModifier.padding()を併用:

kotlin
WindowInsets.systemBars.only(WindowInsetsSides.Top)
    .toPaddingValues()

Android 15以降では、動的なパディング調整が最も信頼性の高いソリューションです。WindowInsetsCompatを活用することで、OSバージョン差異を吸収しながら最適なユーザー体験を提供できます。