Flutter-テーマの設定・適用

テーマの設定とその適用についてまとめてみた。
あとGetXでのテーマの適用について、問題点があったのでその対応などについても書いてみる。

テーマの設定・適用

MaterialAppのテーマ設定

Flutterでテーマを取り扱う場合、トップとしてMaterialAppを使用することになる。

MaterialApp class - material library - Dart API
API docs for the MaterialApp class from the material library, for the Dart programming language.
Use themes to share colors and font styles
How to share colors and font styles throughout an app using Themes.

設定可能なテーマは、全部で4つ。以下パラメータで指定可能なものを出している。

  1. theme
    デフォルトのテーマで、システム設定でいうところのライトテーマでもある。
  2. darkTheme
    システム設定でいうところのダークテーマ。
  3. highContrastTheme
    システム設定で高輝度設定しかつシステム設定でいうところのライトテーマを選択した時に使用されるテーマ。
  4. highContrastDarkTheme
    システム設定で高輝度設定しかつシステム設定でいうところのダークテーマを選択した時に使用されるテーマ。

システム設定の「ライト・ダーク」は、Androidは以下のページで設定する。

これはNexus 6Pをシミュレーターで表示させたもの。
Windows 11は個人設定画面で設定する。

というか、こんなのがあるとは知らなかった。

「高輝度設定」はユーザー補助の方にある。

のだけど、APIヘルプを見る限り2023/03/23時点ではiOS 13以降のみしか適用されていないようなのだけどWindowsでは以下で変更できた。

アクセシビリティのコントラストテーマを無しとそれ以外で変わった。

Windows版に関しては、githubのissuesを覗いて見ると対応したというのが書かれていたのでヘルプ側の修正がされていないのだろう。

設定によりどういったテーマが適用されるか

次に設定されたテーマがどのように適用されるかについて。

パラメータとしてはMaterialAppに渡すパラメータとシステム設定の「ライト・ダーク」「高輝度設定」になる。

themeMode ライト・ダーク 高輝度設定 使用されるテーマ
light 無視される OFF theme
ON highContrastTheme
nullならtheme
dark OFF darkTheme
nullならtheme
ON highContrastDarkTheme
nullならdarkTheme
さらにnullならtheme
system ライト OFF theme
ON highContrastTheme
nullならtheme
ダーク OFF darkTheme
nullならtheme
ON highContrastDarkTheme
nullならdarkTheme
さらにnullならtheme

実装する際の注意点

  1. システム設定の「ライト・ダーク」を有効にしたいのであれば、themeとdarkThemeの両方にテーマの設定を行う必要がある。
  2. themeModeでライト・ダークの設定を切り替える場合は、themeとdarkThemeの両方にテーマの設定を行う必要がある。
  3. themeを自分で切り替えるようなアプリケーションの場合は、themeModeをライトにしておいた方が望ましい。
    • darkThemeがnullなら必ずthemeを使うので特に気にする必要はなく、あくまで推奨。
  4. Android以外もターゲットにする場合は高輝度のテーマをどうするか検討する必要がある。
    適用させる必要があるのであれば、highContrastTheme、highContrastDarkThemeを設定しておく。

アプリケーション内でのダイナミックなテーマの変更

パッケージに依存せず、一番簡単に定義できるのはStreamを使った方法。

Flutter: How to change the MaterialApp theme at runtime
I have a MaterialApp Widget that sets the theme for all Widgets within the app. I'd like to change the MaterialApps them...

上記のは、ライト・ダークの切り替えをboolで渡しているけどThemeDataを渡すというやり方もある。以下はstackoverflowのをThemeDataでやり取りする方法に変更した部分のMainApp側。

StreamController<ThemeData> theme = StreamController();

class MainApp extends StatelessWidget {
  const MainApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<ThemeData>(
        initialData: null,
        stream: theme.stream,
        builder: (context, snapshot) {
          return MaterialApp(
              theme: snapshot.data,
              home: Scaffold(
                  appBar: AppBar(title: const Text("Dynamic Theme")),
                  body: const SettingPage()));
        });
  }
}

 

GetXでのテーマの運用

GetXでのテーマ部分の問題点

  1. darkThemeのデフォルトがThemeData.fallback()を設定されてしまう。
    これは現状ThemeData.light()と同じ。
  2. 高輝度テーマの設定はできない。
    GetMaterialAppのパラメータにhighContrastTheme、highContrastDarkThemeはあるのだけど、これをMaterialAppに渡してない。

1の問題点と回避方法

上に書いたMaterialAppでのテーマ決定を見てほしい。
システムの「ライト・ダーク」設定がダークになっていた場合、theme側にいくらテーマを設定してもThemeData.light()が適用されてしまう形になる。

特に問題なのがGetMaterialAppの呼び出しでdarkThemeとthemeModeの設定をせずに、設定がダークの状態でGet.changeTheme()でテーマの設定をしても、設定したテーマが適用されないという状態になる。

これはMaterialAppに渡すdarkThemeにThemeData.light()を設定していることで、それを使用する形になってしまっていること。

結果として、GetMaterialAppの呼び出しでdarkThemeを設定しない場合はthemeModeにはThemeMode.lightを設定する必要がある。

2の問題と回避方法

MaterialApp側にパラメータを渡していないので、現状これを回避する方法はない。

アプリケーション内でのダイナミックなテーマの変更

基本はGetXに記載されている内容通りにすればいいのだけど、上の1の問題に対応するためdarkThemeは未設定にしておきthemeModeをライトにする。

getx/README.ja-JP.md at master · jonataslaw/getx
Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with Get. - jo...

アプリケーション側でthemeModeでライト・ダークの切り替えを行う場合、Get.changeThemeでtheme、darkThemeの直接の変更はできないので、GetMaterialAppに直接設定するか、GetMaterialControllerを取り出し、そこにテーマを設定する必要がある。

GetMaterialControllerを使った例は以下の通り。
ここではthemeのみを変更しているが、darkThemeもメンバにあるのでそこに変更してもいいし、themeModeも持っているのでそこも一緒に変更するのもいい。
最後updateでウィジェットの再構築が走る。

              TextButton(
                  child: const Text("Light Theme"),
                  onPressed: () {
                    final ctrl = Get.find<GetMaterialController>();
                    ctrl.theme = ThemeData.light();
                    ctrl.update();
                  }),
              TextButton(
                  child: const Text("Dark Theme"),
                  onPressed: () {
                    final ctrl = Get.find<GetMaterialController>();
                    ctrl.theme = ThemeData.dark();
                    ctrl.update();
                  }),

 

コメント

タイトルとURLをコピーしました