【Flutter】ステータスバーの文字色変更の挙動について

## 動作環境
Flutter: 2.8.1
Dart: 2.15.1

## はじめに

Flutterでは`SystemChrome.setSystemUIOverlayStyle`というメソッドが用意されていて、引数に指定した`SystemUiOverlayStyle`に対して、`statusBarBrightness`(iOS)と、`statusBarIconBrightness`(Android)を指定してあげればステータスバーの文字色をBrightnessに合った色に変更することができます。

``` dart
SystemChrome.setSystemUIOverlayStyle(
  SystemUiOverlayStyle(
    statusBarBrightness: Brightness.light,     // for iOS
    statusBarIconBrightness: Brightness.dark,  // for Android
  ),
);
```

下記のコードを実行すると、iOSとAndroidそれぞれステータスバーの文字色が変化しているのが確認できます。


``` dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

/// プッシュで遷移する
Future _push(BuildContext context, Widget nextScreen, {bool animated = true}) {
  Route route = animated
      ? MaterialPageRoute(builder: (context) => nextScreen)
      : PageRouteBuilder(
          pageBuilder: (context, animation1, animation2) => nextScreen,
          transitionDuration: const Duration(seconds: 0),
        );
  return Navigator.of(context).push(route);
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Widget1(),
    );
  }
}

class Widget1 extends StatelessWidget {
  _changeOverlayStyle({
    required bool isLight,
  }) {
    SystemChrome.setSystemUIOverlayStyle(
      SystemUiOverlayStyle(
        statusBarBrightness:
            isLight ? Brightness.light : Brightness.dark, // for iOS
        statusBarIconBrightness:
            isLight ? Brightness.dark : Brightness.light, // for Android
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    // ステータスバーをライトモードにする
    _changeOverlayStyle(isLight: true);
    return Scaffold(
      body: Container(color: Colors.red),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // ステータスバーをダークモードにする
          _changeOverlayStyle(isLight: false);
          // 画面遷移処理
          await _push(context, Widget2());
          // ステータスバーをライトモードに戻す
          _changeOverlayStyle(isLight: true);
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

class Widget2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(color: Colors.orange),
    );
  }
}
```

![first.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/001efc95-0a95-da76-b89b-d34cbadb27cb.gif)


しかし上記のコードはScaffoldに対してAppBarが設定されていません。

`Widget1`(遷移元)と`Widget2`(遷移先)どちらかにAppBarが設定されていると、上記の`_changeOverlayStyle`メソッドで文字色を変更するコードは動作しません。

AppBarがないアプリはほとんどないと思うので、実用的ではなさそう。

## AppBarが設定されている場合のステータスバーの文字色の挙動

AppBarが設定されている場合はデフォルトでは、**AppBarの背景色によってステータスバーの文字色が決まります。**

AppBarを設定すると先ほどの_changeOverlayStyleメソッドで文字色変更処理を加えても動作しません。

これは**AppBarの`backgroundColor`プロパティによって決定されたSystemUiOverlayStyleに上書きされてしまうからです。** [^1]

なので基本的にはAppBarを使っていれば、ステータスバーの文字色の決定は自動的にやってくれるので気にする必要はないでしょう。

では手動で文字色を決定したい場合はどうすれば良いでしょうか？

## 文字色の決定の優先度

コードで文字色を設定する場合は、どのように文字色が決まるかの優先度について把握しておく必要があります。

AppBarを使ってステータスバーの文字色が決定される優先度は下記の通りです。

1. AppBarの`systemOverlayStyle`プロパティ
1. MaterialAppに設定するAppBarThemeの`systemOverlayStyle`プロパティ
1. AppBarの`backgroundColor`プロパティ

1が設定されていれば1によって文字色が決まり、nullの場合は2によって決まる、といった具合です。

### AppBarのsystemOverlayStyleプロパティ
一番優先度の高いプロパティです。

AppBar別に設定できるので画面ごとに文字色を変えたい場合に有効です。

下記のコードはAppBarの背景色を黒色にしているにも関わらず、ステータスバーの文字色も黒くなります。

``` dart
class Widget1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.black,
        systemOverlayStyle: SystemUiOverlayStyle(
          statusBarBrightness: Brightness.light, // for iOS
          statusBarIconBrightness: Brightness.dark, // for Android
        ),
      ),
      body: Container(color: Colors.white),
    );
  }
}
```

![変わらん.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/b63ab6a8-5619-5607-f3d6-b951baeb567b.png)


### MaterialAppに設定するAppBarThemeの`systemOverlayStyle`プロパティ
二番目に優先度が高いプロパティです。

AppBar全体に適応できるので、AppBarごとに設定しなくても良くなります。

下記のコードもAppBarの背景色を黒色にしているにも関わらず、ステータスバーの文字色も黒くなります。

``` dart
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Widget1(),
      theme: ThemeData(
          appBarTheme: AppBarTheme(
        systemOverlayStyle: SystemUiOverlayStyle(
          statusBarBrightness: Brightness.light, // for iOS
          statusBarIconBrightness: Brightness.dark, // for Android
        ),
      )),
    );
  }
}

class Widget1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.black,
      ),
      body: Container(color: Colors.white),
    );
  }
}
```

![変わらん.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/b63ab6a8-5619-5607-f3d6-b951baeb567b.png)


### AppBarのbackgroundColor
一番優先度が低いプロパティです。

文字色を設定する処理を書かなくても良いので一番楽です。

``` dart
class Widget1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.black,
      ),
      body: Container(color: Colors.white),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _push(context, Widget2());
        },
      ),
    );
  }
}

class Widget2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(backgroundColor: Colors.white),
      body: Container(color: Colors.white),
    );
  }
}
```

![自動で変わるよ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/82ada406-5fcc-9bcf-3857-9d7111eef575.gif)

## AppBarあり画面からなし画面に遷移する際の挙動
AppBarがある画面から、AppBarがない画面に遷移する際に文字色を変更したい場合はどうすれば良いでしょうか？

例えば下記の動画のように黒いAppBarの画面から、白色背景に遷移する際には、白い背景に合わせて、ステータスバーの文字色も変化してほしいはずです。

![AppBarがある画面からない画面に遷移する際のサンプル1_AdobeCreativeCloudExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/32590d0f-8faa-a785-f299-e83e77f79c80.gif)

先述した通り、AppBarがある画面では`backgroundColor`によって自動的に文字色を変えてくれますが、AppBarがない画面のことは考えてくれません。

AppBarがない画面で`SystemChrome.setSystemUIOverlayStyle`を呼んであげれば良いでしょうか？
残念ながらそれはうまく動作しません。
なぜならAppBarのインスタンスは保持されたままなので、別画面で呼んであげてもAppBarが自動的に`SystemUiOverlayStyle`を変更するという動作は変わらないからです。

この場合は、下記のコードのようにAppBarがある画面の`systemOverlayStyle`プロパティを、遷移前と遷移後で変更することで解決できます。

``` dart
class _Widget1State extends State<Widget1> {
  bool isLight = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.black,
        systemOverlayStyle: SystemUiOverlayStyle(
          statusBarBrightness: isLight ? Brightness.light : Brightness.dark,
          statusBarIconBrightness: isLight ? Brightness.dark : Brightness.light,
        ),
      ),
      body: Container(color: Colors.white),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // 遷移前に文字色変更
          setState(() {
            isLight = !isLight;
          });
          // 遷移後に元に戻す
          await _push(context, Widget2());
          setState(() {
            isLight = !isLight;
          });
        },
      ),
    );
  }
}

class Widget2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(color: Colors.white),
    );
  }
}
```

![遷移前と遷移先で変えるよ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/04360b25-3120-cd67-f1a9-4ddf09ab62c3.gif)


## まとめ
ステータスバーの文字色変更については下記の方針で良いかなと思います。

* 基本的には文字色はAppBarの`backgroundColor`による自動化に任せる。
* アプリ全体でAppBarの背景色が決まっており、自動的に決めたくない場合はAppBarThemeの`systemOverlayStyle`を設定し、文字色を統一する。
* AppBarがない画面など、別個に文字色を調整する必要がある場合はAppBarの`systemOverlayStyle`プロパティを設定する。

以上です。

[^1]: [公式ドキュメント参考](https://api.flutter.dev/flutter/services/SystemChrome/setSystemUIOverlayStyle.html)