アプリ開発備忘録: Android 15からはEdge-to-Edge表示が強制適用に (Part 3)

Android 15以降から画面のEdge-to-Edge表示が強制適用になって、同時に画面上部の時間やバッテリーの残量が表示される「ステータスバー」の背景が透明になりました。

パート2」では、強引にスタータスバーの背景色を設定する方法を確認しましたが、この方法だと背景色の色を設定できるタイミングに少しの制限が入ります。

なので今回は。違う方法でステータスバーの背景色を設定する方法をこの備忘録に残しておきます。

[目次]

パート 1
  • Edge-to-Edge表示 (Android アプリ)
  • テストアプリを使って確認
  • ターゲットSDKバージョンを35に変更
  • ステータスバーとAppBarが重ならない様にする例 (XMLレイアウト)

パート 2
  • Android15よりも前のデバイスでもEdge-To-Edge表示
  • ステータスバーの背景色
パート 3 (このメモ)

パート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()
      }
    }

  }

    :   :   :   : 

この方法だとコールバックを使って色を設定している為、初めて画面を表示した場合や、画面の縦横の向きを変えた時など、画面がシステムによって描き直される際に背景色設定が適応されます。

ステータスバーの色がいつも同じの場合は全く問題のない解決方法ですが、背景色に動的な変化を加えたい場合は、システムによる画面の再描画を発生させる必要が出てきたりします。。

こうなるとややっこしくなるばかりなので、別の解決法を考えてみました。。


別の方法

考えた方法は、無理矢理ステータスバーの背景を設定するのではなく、アプリ側でステータスバーの後ろに、ステータスバーと同じ大きさの長方形を色付きで表示すえる方法です。

アプリで表示すれば、動的に変わるステータスバーの背景色などを比較的簡単に実現させる事ができます。

図解するとこんな感じになります:

10:10AM
アプリ画面
10:10AM
アプリ画面

この方法だと、画面のスクロールに合わせてステータスバーの背景を透明にしたり、アニメーションを使って背景色を常時変化させる事も可能になります。

また、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版のアプリに、この仕様のステータスバー背景色を導入する為にアップデートしています。

例:


追記

 

因みに、Android 15よりも前のデバイスでは昔ながらの背景色の設定が有効になっているとその背景色が優先して表示される為、全てを透明にする必要があります。

  • テーマ(Theme)のstatusBar Color
  • setStatusBarColorなどを使ってコード内で背景を設定している場合


コメント

このブログの人気の投稿

アプリ開発備忘録: ② Android 15からはEdge-to-Edge表示が強制適用に (Part 2)

『コメ欄』用カスタムCSS - L◯NE風 (ツイキャス) - OBSのブラウザでも使えます。

アプリ開発備忘録: ① Android 15からはEdge-to-Edge表示が強制適用に (Part 1)

[OBS] Twitchコメント欄向けCSSカスタマイザー (試作)

[ツイキャス配信・閲覧支援ツール] キャスポケットツール: 初期設定

ツイキャスで他の人がサポートしている人って見えますか? (キャスポケットツール)

ブロガー (Blogger) 記事のゴミ箱はどこ? (AIはあるって言っていますが・・

悲劇: ビングでもリダイレクションエラーが発生??