テーマの設定とその適用についてまとめてみた。
あとGetXでのテーマの適用について、問題点があったのでその対応などについても書いてみる。
テーマの設定・適用
MaterialAppのテーマ設定
Flutterでテーマを取り扱う場合、トップとしてMaterialAppを使用することになる。
設定可能なテーマは、全部で4つ。以下パラメータで指定可能なものを出している。
- theme
デフォルトのテーマで、システム設定でいうところのライトテーマでもある。 - darkTheme
システム設定でいうところのダークテーマ。 - highContrastTheme
システム設定で高輝度設定しかつシステム設定でいうところのライトテーマを選択した時に使用されるテーマ。 - 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 |
実装する際の注意点
- システム設定の「ライト・ダーク」を有効にしたいのであれば、themeとdarkThemeの両方にテーマの設定を行う必要がある。
- themeModeでライト・ダークの設定を切り替える場合は、themeとdarkThemeの両方にテーマの設定を行う必要がある。
- themeを自分で切り替えるようなアプリケーションの場合は、themeModeをライトにしておいた方が望ましい。
- darkThemeがnullなら必ずthemeを使うので特に気にする必要はなく、あくまで推奨。
- Android以外もターゲットにする場合は高輝度のテーマをどうするか検討する必要がある。
適用させる必要があるのであれば、highContrastTheme、highContrastDarkThemeを設定しておく。
アプリケーション内でのダイナミックなテーマの変更
パッケージに依存せず、一番簡単に定義できるのはStreamを使った方法。
上記のは、ライト・ダークの切り替えを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でのテーマ部分の問題点
- darkThemeのデフォルトがThemeData.fallback()を設定されてしまう。
これは現状ThemeData.light()と同じ。 - 高輝度テーマの設定はできない。
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をライトにする。
アプリケーション側で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();
}),
コメント