アプリ開発備忘録: Android 15からはEdge-to-Edge表示が強制適用に (Part 3)
Android 15以降から画面のEdge-to-Edge表示が強制適用になって、同時に画面上部の時間やバッテリーの残量が表示される「ステータスバー」の背景が透明になりました。
「パート2」では、強引にスタータスバーの背景色を設定する方法を確認しましたが、この方法だと背景色の色を設定できるタイミングに少しの制限が入ります。
なので今回は。違う方法でステータスバーの背景色を設定する方法をこの備忘録に残しておきます。
パート2で確認した背景色を設定する方法
import android.graphics.Color import android.os.Build import android.view.View import android.view.Window import android.view.WindowInsets : : : : class MainActivity : ComponentActivity() { fun setStatusBarColor(window: Window, color: Int) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // SDK 30 (Android 11) + 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) WindowInsets.Builder(insets) .setInsets(WindowInsets.Type.statusBars(), Insets.of(0,0,0,0), //statusBarのinsetを削除 ).build() } } else { // For SDK 29 (Android 10) and below window.statusBarColor = color } } : : : : override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { enableEdgeToEdge() } setStatusBarColor(window, Color(0xFF1B5E20)) actionBar?.hide() super.onCreate(savedInstanceState) if (useCompose) { setContent { App() } } } : : : :
この方法だとコールバックを使って色を設定している為、初めて画面を表示した場合や、画面の縦横の向きを変えた時など、画面がシステムによって描き直される際に背景色設定が適応されます。
ステータスバーの色がいつも同じの場合は全く問題のない解決方法ですが、背景色に動的な変化を加えたい場合は、システムによる画面の再描画を発生させる必要が出てきたりします。。
こうなるとややっこしくなるばかりなので、別の解決法を考えてみました。。
別の方法
考えた方法は、無理矢理ステータスバーの背景を設定するのではなく、アプリ側でステータスバーの後ろに、ステータスバーと同じ大きさの長方形を色付きで表示すえる方法です。
アプリで表示すれば、動的に変わるステータスバーの背景色などを比較的簡単に実現させる事ができます。
図解するとこんな感じになります:
この方法だと、画面のスクロールに合わせてステータスバーの背景を透明にしたり、アニメーションを使って背景色を常時変化させる事も可能になります。
また、OSに関係なくステータスバー背景色を指定出来る為、必要であれば、iOS版のアプリでもステータスバーの背景色を指定する事が可能になります。
例: JetPack Compose
例として、Jetpack Composeで画面スクロールに合わせてステータスバーの背景を半透明にするコードを次に:
val statusBarColor = Color(0xFF1B5E20) //ステータスバーの背景色 val appBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val lazyColumnListState = rememberLazyListState() val scrollOffset = if (0 != lazyColumnListState.firstVisibleItemIndex) 1f else (lazyColumnListState.firstVisibleItemScrollOffset / 200f) val alpha = 1f - scrollOffset.coerceIn(0.0f, 0.5f) val statusBarBgColorState = animateColorAsState( targetValue = statuBarColor.copy(alpha = alpha), animationSpec = tween(durationMillis = 300) ) Box ( modifier = Modifier.fillMaxSize() .background(color = MaterialTheme.colorScheme.surface), contentAlignment = Alignment.TopCenter, ) { StatusBarBackground( modifier = Modifier.align(Alignment.TopCenter).zIndex(1f), color = statusBarBgColorState.value ) Column ( modifier = Modifier.fillMaxSize(), ) { TopAppBar ( scrollBehavior = appBarScrollBehavior, : : : : : title = { Text("App Bar") } ) LazyColumn ( state = lazyColumnListState, modifier = Modifier.fillMaxSize() .nestedScroll(appBarScrollBehavior.nestedScrollConnection), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { : : : : : } } }
次の「StatusBarBackground」がステータスバーの下に表示される長方形になります。
@Composable
fun StatusBarBackground (
modifier: Modifier = Modifier,
additionalHeight: Dp = 0.dp,
color: Color
) {
Box(
modifier = modifier.fillMaxWidth()
.height(additionalHeight +
WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
.background(color = color)
)
}
この方法では画面の表示に「Scafold」を使っていませんが、Composeので、「Scaffold」はWindowInstes情報を元に自動でスタータスバー分の隙間が画面上部に追加される為、ステータスバーの下にアプリの表示コンテンツが重ならない様になっています。
この例では「Box」を使って「StatusBarBackground」の下にスクロール部の「LayzyColumn」が表示される様にしているので、画面が上にスクロールされるとステータスバーの背景が50%の透明度に透けて、その下にスクロールされた内容が透けて見える仕様になっています。
本来、ステータスバーの色がアプリバーの色と違うのはAndroid 15以前の Android特有のデザインで、iOSアプリではあまり見かけないデザインだと思います。
でも、最近だと、ComposeのMultiplatform版とKotlinのMultiplatform版が割と安定して来ているので、Kotlin/ComposeでのAndroidとiOSアプリの並行開発が一般化すれば、このAndroid的なデザインを引き継いだiOSアプリでも見かける様になるかもしれません (?)
例: Flutter
ここまで「Flutter」で開発するAndroidアプリについては全く触れてはいませんでしたが、現行のFlutterは採用されているActivityは、AppCompatActivityでもなく、ComponentActivityでもなく、Flutter独自のFullterActivityが採用されている為、Edge-to-Edge表示になるかどうかは、アプリを実行するAndroidのバージョンには関係なく、Flutter SDKのバージョンで決まる様で、enableEdgeToedge()と言ったメソッドも(必要が)ありません。
Stackクラスを使ってステータスバーの背景色の長方形を重ねれば良いだけになります。
@override
Widget build(BuildContext context) {
Stack(
children: <Widget> [
CustomScrollView(
controller: _scrollController,
slivers: <Widget> [
SliverAppBar( //App バー
toolbarHeight: kToolbarHeight,
: : : :
),
: : : : (画面コンテンツ)
],
),
if (Platform.isAndroid)
StatusBarBackground(controller: _scrollController,)
],
),
}
Flutter版の「StatusBarBackground」は次の様になります。
class StatusBarBackground extends StatefulWidget {
const StatusBarBackground({super.key,
this.controller,
});
final ScrollController? controller;
@override
State<StatusBarBackground> createState() => _StatusBarBackScrimState();
}
class _StatusBarBackScrimState extends State<StatusBarBackground> {
double? _offset;
void _listener() {
_offset = (true == widget.controller?.hasClients)? (widget.controller?.offset) : null;
if (kDebugMode) {
print("[StatusBarBackground] Scroll Listener: offset=$_offset");
}
setState(() { });
}
@override
void initState() {
super.initState();
final controller = widget.controller;
if (null != controller) {
controller.addListener(_listener);
}
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
width: double.maxFinite,
height: MediaQuery.of(context).padding.top,
decoration: BoxDecoration(
color: myPrimaryDark.withAlpha(
(10.0 > (_offset ?? 0.0))? 255: 120
),
),
);
}
}
引数のcontollerに渡される「_scrollController」はScrollControllerのインスタンスになりますが、CustomScrollviewにも渡されている為、CustomScrollviewの上スクロールに合わせてステータスバーの背景色が半透明になる仕様になっています。
※: 現在、Flutterで開発したAndroid版のアプリに、この仕様のステータスバー背景色を導入する為にアップデートしています。
例:
- IP アドレスロガー
- ぴゅこらーと (一部の画面で)
- シリアル SMC テスタ
追記
因みに、Android 15よりも前のデバイスでは昔ながらの背景色の設定が有効になっているとその背景色が優先して表示される為、全てを透明にする必要があります。
- テーマ(Theme)のstatusBar Color
- setStatusBarColorなどを使ってコード内で背景を設定している場合
コメント
コメントを投稿