アプリ開発備忘録: ② Android 15からはEdge-to-Edge表示が強制適用に (Part 2)
Android 15から画面の「Edge-to-Edge」表示が強制適用になりましたが、この変更に関連した開発コードの備忘録をここにメモしておきます。
パート1では、Android/Kotlinで実装されたテストアプリを使って、最小限のコード変更として、Android 15以降のデバイスでだけ「Edge-to-Edge」表示が有効になる仕様の画面表示の確認をしました。
パート2ではAndroid 15よりも前のデバイスでも「Edge-to-Edge」有効にして、なおかつ、ステータスバーの背景色を設定する方法を確認してみます。
[目次]
パート 1
- Edge-to-Edge表示 (Android アプリ)
- テストアプリを使って確認
- ターゲットSDKバージョンを35に変更
- ステータスバーとAppBarが重ならない様にする例 (XMLレイアウト)
パート 2 (このメモ)
パート 3 (準備中)
Android15よりも前のデバイスでもEdge-To-Edge表示
ターゲットSDKバージョンが35以降の場合、Android 15以降のデバイスではEdge-to-Edge表示が強制的に有効になります。
逆に、Android 15よりも前のデバイスでは、ターゲットSDKバージョンが35以降の場合、「enableEdgeToedge()」というメソッドを使ってEdge-to-Edge表示を有効にしていないと、機種によってはアプリの周りに何も表示されない空白が現れたりします。
Android 14デバイス上でEdge-to-Edge表示を有効にしていない場合
次のスクショは、パート1でターゲットSDKが35でビルドされたアプリを、ランドスケープ表示にした場合の例になります (このバージョンのアプリではenableEdgeToEdgeメソッドを呼び出さない仕様になっています。):
パート1で、同じアプリをAndroid 14上で表示して確認した際は、画面の向きがポートレートだったので気がつきませんでしたが、画面に切り抜きがあるデバイスだと、ランドスケープにした場合は、切り抜き部分の表示が真っ白になっていました。
あくまでも予想ですが、アプリがターゲットSDKが35なので、アプリの表示範囲は全画面表示になっているのに、アプリの実際の表示コンテンツは全画面表示の仕様になっていないのでInsets情報に基づいて表示範囲が調節されているのではないかと思われます。
アプリの機能的には支障がない表示結果ですが、ビジュアル的にはちょっとよろしくない結果なので、まず、Edge-to-Edge表示を有効にして表示を改善出来るか確認してみます。
enableEdgeToEdge()でEdge-to-Edgeを有効化
SDK 15以降では、Android 15よりも前のデバイスでもEdge-to-Edge表示を有効に出来る「enableEdgeToedge()」というメソッドが追加されていているので、古いデバイスの表示をAndroid 15以降の 新旧のデバイスでアプリのデザインを統一する事ができます。
ただ、このenableEdgeToedge()、実際に使ってみたところ、少しクセが強かったです。。
結果から言うと、enableEdgeToedge()を実行する事で、何故か「ActionBar (アクションバー)」が手前 (Z軸上)に表示される様になりました。
また、デフォルトでEdge-to-Edgeが有効になっているAndroid 15でこのenableEdgeToedge()を実行した場合、ActionBarが表示されました (コメントアウトした場合は表示されませんでした)。
次はAndroid 15で表示した時のスクショになります:
この「ActionBar」はマテリアルデザインで「Toolbar」とか「AppBar」が追加される前に使われていたタイトルバーで、最近だとThemeなどで非表示設定されているはずなのですが、どうもenableEdgeToedge()を実行する事でそれらの設定が影響を受けてしまう様です。😕😕
ActionBarを隠す
Androidはオープンソースなので、ソースコードを掘り下げていけば原因を見つけられそうですが、そこまで手が回っておらず、何故ActionBarが表示されるのかは不明ですが、次のコマンドのどちらかで古いアクションバーを非表示に出来ます。
supportActionBar?.hide() //効果無し actionBar?.hide()
一行目の「supportActionBar?.hide()」は、Activityが、XMLレイアウトの時に使われるAppCompatActivityの場合に有効で、JetPack Composeの時によく使われるComponentActivityには存在しないメソッドになります。
なのでComponentActivity又はその派生クラスをActivityとして使っている場合はコメントアウトする必要があります。
enableEdgeToedge()は、ActivityのonCreate(...)のはじめの方で呼び出す必要があるみたいなので、次の様にsuper.onCreate(...)の前辺りに配置すると良いと思います。
import androidx.activity.enableEdgeToEdge : : : : override fun onCreate(savedInstanceState: Bundle?) { : : : : if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { enableEdgeToedge() supportActionBar?.hide() //効果無し? actionBar?.hide() } super.onCreate(savedInstanceState) : : : : }
※: Android 15 (SDK 15、コードネームVANILLA_ICE_CREAM)以降のデバイスではEdge-to-Edge表示が既に有効になっている為、このコードではenableEdgeToedge()を実行しない仕様になっています。
これによって、古いデバイスでもEdge-to-Edgeが有効になって、画面を横向きにした場合に全画面でも表示される様になるので、切り抜きがある画面上でもアプリが全画面表示になりました。
次は「とにかく」ステータスバーの背景色を設定する方法を検証してみます。
ステータスバーの背景色
Android 15 (SDK 35) のEdge-to-Edge表示に伴って、ステータスバーの背景色が透明に設定される他、それまでステータスバーの背景色を設定するのに使われていた"android.view.window"クラスのメソッドの「setStatusBar(Int)」も廃止されました。
Androidの歴史に精通している訳ではないので、いつからは知りませんが、マテリアルデザイン2が主流だった頃だと、ステータスバーの背景色がAppBarの背景色よりも少し暗くなっているAndroidアプリが多かった気がします。
現行のマテリアルデザイン3になって、ステータスバーの背景色は確かオプショナルになっていたと思うので、iOSアプリの様にステータスバーとAppBarの背景色が同じになっているアプリが増えたと思います。
個人的にはステータスバーの背景色がAppBarの背景色と違う方がステータスバーの情報が読みやすい気がするので、開発中の多くのアプリで、いまだに、ステータスバーの背景色をAppBarの背景色よりも暗く色に設定していました。。
Android 15で廃止になったsetStatusBarColor()
普通は、メソッドが廃止されてもその後のSDKバージョンでも当分の間は使える場合が多いのですが、このメソッドは廃止と同時に機能しなくなっています。
SDKのAPIリファレンスによると:
と、「代わりにステータスバーのWindowインセットの後ろの適切な背景を塗る様に」😶?😶?といった意味のことが書いてありますが、個人的な読解力が欠けているのか、これだけでは理解ができませんでした。。。
ステータスバーの背景を着色する方法
スタック・オーバーフローで同様な事「How to change the status bar color in Android 15+?」が質問されていましたが、この質問に付いていた回答の一つに次の様なKotlinのコードスニペットがありました:
import android.graphics.Color import android.os.Build import android.view.View import android.view.Window import android.view.WindowInsets fun setStatusBarColor(window: Window, color: Int) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { // Android 15+ window.decorView.setOnApplyWindowInsetsListener { view, insets -> val statusBarInsets = insets.getInsets(WindowInsets.Type.statusBars()) view.setBackgroundColor(color) // Adjust padding to avoid overlap view.setPadding(0, statusBarInsets.top, 0, 0) insets } } else { // For Android 14 and below window.statusBarColor = color } }
このコードでは、Android 14以前の場合はstatusBarColorで色が設定されますが、Android 15 (VANILLA_ICE_CREAM) 以降の場合は、ActivityのwindowのデコレーションViewに、setOnApplyWindowInsetListenerでリスナーが設定しているので、WindowInsetの設定が変更される毎にwindowのデコレーションViewに該当する部分の背景色をsetBackdroundColorで塗っています。
そして、setPaddingでWindowの上部にステーターバーの高さのパッドを追加しているので、Window内にコンテンツが表示されても背景色の部分が上部に残る様になっています。
アプリに組み込むには、上記の"setStatusBarColor"をMainActivityのonCreate()の頭で呼べば良いみたいです。
(※ 詳しくはAndroidデベロッパーのリファレンスにある「システムバーインセット」が参考になるかもしれません。)
アプリのステータスバーの下に来る部分を任意の色にするという「本当にステータスバーの背景色を設定する方法」になっています。
備考
このコードスニペットは、setStatusBarColorメソッドが無効になってしまったAndroid 15 (コードネーム VANILLA_ICE_CREAM) 以降のデバイスでステータスバーの背景色を設定したいという質問への答えとして投稿されたコードなので、デバイスが:
- Android 15以降の場合はInsets情報を使ってステータスバーの背景色を設定、
- Android 15より前の場合はsetStatusBarColorを使う
という仕様になっていると思います。
InsetsクラスはAndroid 30 (コードネーム R)から既に導入されている為、 このコードを採用したい場合は、コード内の「Build.VERSION_CODES.VANILLA_ICE_CREAM」を「Build.VERSION_CODES.R」に変更して使う事をお勧めします。
Android 15デバイス上で確認
次は上記のAndroid 15でステ-タスバーの背景色を塗るコードを実装した場合のスクショです。
Android 15なので、enableEdgeToEdge()は実行しない仕様になっていましたが、上記のコードを実行すると何故かまたActionBarが表示されました。😕😕
Action Barを非表示
enableEdgeToEdgeを確認した時と同様にactionBar?.hide()でActionBarを非表示にしました。
XMレイアウトの方は問題ありませんが、JetPack Composeの方はAppBarがステータスバーの高さ分、背が高くなっています。
どうもsetOnApplyWindowInsetsListener内でWindowの上部にパッドを追加してInsetを変更しているのでJetPack Composeの場合は影響を受ける様※です。
※: 表示にScaffoldを使っているので表示がInsets情報に合わせて変更されるみたいです。。。
JetPack ComposeのInsets調整
AppBarのInsetsを調整すれば上のスクショの様に改善されますが、次にJetPack ComposeでAppBarの高さを調整する方法を次に2つ:
TopAppBarのInsetsを調整 (setStatusBarColor使用時)
ComposeのTopAppBarのmodifierで、consumeWindowInsetsを使ってAppBarの上部のインセットからステータスバー分の高さ減らして調整をします。
TopAppBar(
modifier = Modifier.let { it ->
if (Build.VERSION_CODES.R <= Build.VERSION.SDK_INT) //SDK30 +
it.consumeWindowInsets(WindowInsets.statusBars) else it
}... ,
: : : :
) { ...
この方法だと、アプリ内の影響するAppBar全てにこの設定を追加する必要がありますが、JetPack Composeに割と慣れている人なら理解し易い対処法かもしれません。
背景色の設定に使ったsetStatusBarColorの修正
上記のfun setStatusBarColor(...)内の「 window.decorView.setOnApplyWindowInsetsListener {...}」のブロックの最後の行にある 「inset」を削除して、代わりに次の様に「WindowInsets.Builder(insets)」で始まる3行を追加します。
fun setStatusBarColor(window: Window, color: Int) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 15+ window.decorView.setOnApplyWindowInsetsListener { view, insets -> : : : : view.setPadding(0, statusBarInsets.top, 0, 0)insets//追加: ↓ 開始 WindowInsets.Builder(insets) .setInsets(WindowInsets.Type.statusBars(), Insets.of(0,0,0,0), //statusBarのinsetを削除 ).build() //追加: ↑ 終わり } else { : : : : } }
この方法だとJetPack Composeだけで構成されるactivity場合であれば1カ所で対処出来るシンプルな対処方になっています。
ただ、XMLレイアウトとJetPack Composeの両方が混合したactivityの場合には、XMLレイアウトの方に支障が出る場合があるので向いていないかもしれません。。
備考
この修正では、WindowInsets.Builderを使って、既存のInset情報からステータスバーのInsets情報をだけを削除して、その結果をLisnerの結果に渡しています。
fun setStatusBarColor(...) 内の「inset」を
という意見もあるかもしれませんが、WindowInsetsCompat.CONSUMEDを返してしまうとナビゲーションバーやジャスチャーなどのInsets情報の全てを破棄してしまう事になります。
setStatusBarColorは、引数で渡されるactivityのwindowに対して処理をする為、activityのwindowの下の階層に属する全てのUIでInsets情報が無視される事になります。
なので、同じactivity内でナビゲーションバー関連のInsets調節や、ジャスチャーエリアを避ける様な調節が出来なくなる可能性があります。
一応、これでステータスバーの背景の色を設定出来る様になりましたが、使ってみると何かと不都合があったので、別の方法をパート3にメモしたいと思いましす。
また、これまでは、Android/KotlinでXMLレイアウトかJetPack Composeを使った場合のアプリについてのメモでしたが、Flutterで開発するアプリについても後でメモにしていきたいかと思います。
コメント
コメントを投稿