こんにちは、naberyo(@error96num)です。今年4月に STORES へ入社し、 STORES ブランドアプリ のAndroidエンジニアをしています。
Androidエンジニアのみなさま、アプリのターゲットSDKは35に上げましたか?
もし上げたなら、エッジツーエッジ対応もお済みでしょうか?
Android 15 (SDK 35) 以降をターゲットとするアプリでは、画面の端から端までコンテンツが描画される「エッジツーエッジ」がデフォルトで適用 されるようになりました。この変更は、没入感あるUXを実現する素晴らしい機能ですが、対応を間違えると思わぬ落とし穴にはまる可能性があります。
先日、弊社のYamatonが『edge-to-edge対応Tips』という包括的な解説記事を公開しました。まだご覧になっていない方は、ぜひ一度チェックしてみてください。
この記事ではその解説を踏まえつつ、私がエッジツーエッジ対応で直面した「enableEdgeToEdge
関数」に関する意外な落とし穴と、その対処法を紹介します。同じ問題で悩むことがないよう、ぜひ参考にしてください!
はじめに
アプリが SDK 35 以降をターゲットとしている場合、Android 15 以降のデバイスでは、エッジツーエッジが自動的に有効になります。これは、アプリ全体のデザインやUXを洗練させる素晴らしい機能です。
一方で、SDK 34 以前のバージョンでは、エッジツーエッジを有効にするためには、明示的な設定が必要です。この際、多くの開発者が頼りにするのが、enableEdgeToEdge
という便利な関数です。
落とし穴にハマるまで
Androidの公式ドキュメントには、次のように説明されています。
Activity
のonCreate
でenableEdgeToEdge
を呼び出して、エッジ ツー エッジを手動で有効にします。setContentView
の前に呼び出す必要があります。
ふむふむ。なるほど、簡単じゃないか!
私は意気揚揚と、各Activityに次のコードを追加しました。
override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) ... }
これでSDK 34以前のエッジツーエッジ対応完了! そう思った矢先、ビルドしたアプリを確認すると、なんと次のような状態になっていたのです。
Before | After |
---|---|
なんですって。画面最上部、ステータスバーのシステムアイコンが見えなくなっている...!?
正確に言うと、黒いアイコンが黒い背景と同化し、完全に消えたように見えてしまったのです。
状況解説
この状態はライトモードで暗い色のステータスバーを使用している、あるいはダークモードで明るい色のステータスバーを使用しているアプリで、enableEdgeToEdge
をデフォルト引数のまま呼び出したときに起こります。
また、ダークモードをサポートしていないアプリで暗い色のステータスバーを使っているケースでも同様です。
公式ドキュメントには、次のように説明されています。
デフォルトでは、
enableEdgeToEdge()
はシステムバーを透明にします。ただし、ステータスバーに半透明のスクリムが適用される 3 ボタン ナビゲーション モードを除きます。システム アイコンとスクリムの色は、システムのライトモードまたはダークモードに基づいて調整されます。
ここで特に重要なのは、システムアイコンの色が、システムのライト/ダークモードに従って決定される という点です。
enableEdgeToEdge
とデフォルト引数の関係
実際にenableEdgeToEdge
関数のコードを追ってみると、statusBarStyle
のデフォルト引数として SystemBarStyle.auto
が指定 されています。
fun ComponentActivity.enableEdgeToEdge(
statusBarStyle: SystemBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT),
navigationBarStyle: SystemBarStyle = SystemBarStyle.auto(DefaultLightScrim, DefaultDarkScrim)
) {
...
}
SystemBarStyle.auto
の中では、以下のような実装でシステムがダークモードかどうかを判断します。
@JvmStatic @JvmOverloads fun auto( @ColorInt lightScrim: Int, @ColorInt darkScrim: Int, detectDarkMode: (Resources) -> Boolean = { resources -> (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES } ): SystemBarStyle { return SystemBarStyle( lightScrim = lightScrim, darkScrim = darkScrim, nightMode = UiModeManager.MODE_NIGHT_AUTO, detectDarkMode = detectDarkMode ) }
そして、この detectDarkMode
を用いて、エッジツーエッジ用のAPI (EdgeToEdgeApiXX
) がステータスバーとナビゲーションバーのスタイルを切り替えており、具体的には ライトモード時は黒いアイコン、ダークモード時は白いアイコン を描画する挙動を設定しています。
@JvmName("enable") @JvmOverloads fun ComponentActivity.enableEdgeToEdge( statusBarStyle: SystemBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT), navigationBarStyle: SystemBarStyle = SystemBarStyle.auto(DefaultLightScrim, DefaultDarkScrim) ) { val view = window.decorView val statusBarIsDark = statusBarStyle.detectDarkMode(view.resources) val navigationBarIsDark = navigationBarStyle.detectDarkMode(view.resources) val impl = Impl ?: if (Build.VERSION.SDK_INT >= 30) { EdgeToEdgeApi30() } else if (Build.VERSION.SDK_INT >= 29) { EdgeToEdgeApi29() } else if (Build.VERSION.SDK_INT >= 28) { EdgeToEdgeApi28() } else if (Build.VERSION.SDK_INT >= 26) { EdgeToEdgeApi26() } else if (Build.VERSION.SDK_INT >= 23) { EdgeToEdgeApi23() } else if (Build.VERSION.SDK_INT >= 21) { EdgeToEdgeApi21() } else { EdgeToEdgeBase() } .also { Impl = it } impl.setUp( statusBarStyle, navigationBarStyle, window, view, statusBarIsDark, navigationBarIsDark ) impl.adjustLayoutInDisplayCutoutMode(window) }
例えば、EdgeToEdgeApi29
の実装では、次のようにステータスバーやナビゲーションバーのスタイルを更新しています。
WindowInsetsControllerCompat(window, view).run { isAppearanceLightStatusBars = !statusBarIsDark isAppearanceLightNavigationBars = !navigationBarIsDark }
黒いアイコンが背景に同化する原因
こうした仕組みにより、「ライトモード=明るいステータスバーで黒いアイコン」「ダークモード=暗いステータスバーで白いアイコン」 という前提がデフォルトになっています。
- ライトモードなのにステータスバーを暗い色で設定しているアプリの場合、黒いアイコンが暗い背景に溶け込み、消えたように見えてしまいます。
- ダークモードなのにステータスバーを明るい色にしている場合も、白いアイコンが明るい背景に溶け込み、消えたように見えてしまいます。
まとめ:enableEdgeToEdge
のデフォルト動作
SystemBarStyle.auto
はシステムのモード設定(ライト/ダーク)に基づいて、ステータスバーの色とアイコン色を自動的に切り替える。- デフォルトでは、ライトモード = 黒アイコン / ダークモード = 白アイコン という前提がある。
- アプリ固有でステータスバーやナビゲーションバーを独自の色にしている場合、この前提と合わずにアイコンが背景に紛れ込む可能性がある。
対応
これまで紹介したように、enableEdgeToEdge
関数はシステムのライト/ダークモードに応じてステータスバーのスタイルを自動調整します。
しかし、アプリ側でステータスバーの色を独自に設定している場合や、ライト/ダークモードの切り替えをサポートしない場合には、デフォルトの挙動だけでは問題が生じる可能性があります。
そこで、本章では enableEdgeToEdge
のデフォルト引数を理解した上で、正しく使うためのポイント を紹介します。
まずは、デフォルト引数である SystemBarStyle.auto
のKDocから引用・翻訳してみましょう。
このスタイルは自動的にダーク モードを検出し、ステータス バーとナビゲーション バーのそれぞれに推奨されるスタイルを適用します。このスタイルがアプリで機能しない場合は、
dark
またはlight
の使用を検討してください。
ここにあるとおり、自動的な判断がアプリの要件と合わない場合には、dark
やlight
への切り替えが推奨されています。次のセクションでは、最小構成の対応例から、より柔軟なアプローチまで順を追って解説していきます。
最小構成の対応例
例えば、システムのライト/ダークモード設定によらず、常に暗い色のステータスバーを表示したい場合には、enableEdgeToEdge
を以下のように呼び出します。
override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge( statusBarStyle = SystemBarStyle.dark( scrim = Color.TRANSPARENT, ), ) super.onCreate(savedInstanceState) ... }
より柔軟な対応例
ここからはオプショナルな内容ですが、 STORES ブランドアプリ ではもう少し柔軟にステータスバーの色を制御しています。
「システムのモード設定とは無関係に、アプリのテーマでステータスバーを統一したい」「複数のテーマを切り替えたい」など、アプリ独自の要件がある場合に参考になるアプローチです。
まず、次のような拡張関数を定義します。これは、アプリテーマで設定された windowLightStatusBar
の値を参照し、エッジツーエッジを適用する前のステータスバー色を引き継ぐための仕組みです。
fun ComponentActivity.enableEdgeToEdgeForBrandApp() { when (windowLightStatusBar) { true -> enableEdgeToEdge( statusBarStyle = SystemBarStyle.light( scrim = Color.TRANSPARENT, darkScrim = Color.TRANSPARENT, ), ) false -> enableEdgeToEdge( statusBarStyle = SystemBarStyle.dark( scrim = Color.TRANSPARENT, ), ) } }
ここで、windowLightStatusBar
はエッジツーエッジ適用前のステータスバー色を示し、以下のように定義します。
val Context.windowLightStatusBar: Boolean get() { val typedValue = TypedValue() theme.resolveAttribute(android.R.attr.windowLightStatusBar, typedValue, true) return typedValue.data != 0 // 0 (false) is dark mode, -1 (true) is light mode }
この拡張関数を Activity
の onCreate
で呼び出すことで、エッジツーエッジを有効にしつつ、アプリ側が指定したテーマ色に合わせてステータスバーのスタイルを適用できるようになります。
override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdgeForBrandApp() super.onCreate(savedInstanceState) ... }
解決
以上の対応で、無事にステータスバーのアイコンが正しく表示されるようになりました。
おわりに
この記事では、enableEdgeToEdge
関数のデフォルト引数が、アプリの表示にどのような影響を及ぼすのかを解説しました。特に、システムのライト/ダークモード設定との依存関係を理解することで、エッジツーエッジをより正しく・効果的に活用できるようになります。
- 最小構成の対応例では、システムのモード設定に影響されず「常に暗い色」を適用する方法を紹介しました。
- より柔軟な対応例では、アプリ独自のテーマ設定に合わせてステータスバーを制御する手法を示しました。
まずは、enableEdgeToEdge
のデフォルト挙動をアプリで試し、自分のアプリがどのように表示されるかを確認してみてください。その上で、アプリが採用しているテーマやデザイン方針に合ったスタイルを選択し、エッジツーエッジを最大限に活用して没入感のあるUXを提供していきましょう!
今後もAndroidのUI仕様はアップデートが続くため、公式ドキュメントのチェックや実機でのテストは欠かせません。この記事が、その一助となれば幸いです。