Flutterにて複数のThemeをUIから切り替える方法について、この記事を紹介することで説明します。
概要
以下のようにBlocを用いた状態管理によりThemeDataを変更します。
- 複数のThemeDataを定義しておく
- ThemeDataを切り替えるBlocを作成する
- ThemeDataを切り替えるButtonを押した際にEventをBlocに送る
- BlocがThemeDataを持つStateをViewに通知する
- MaterialAppのthemeプロパティがStateのThemeDataを参照する
複数のThemeDataを定義しておく
このサンプルでは以下の4種類のThemeを切り替えます。お好みで増やすことも可能です。
ThemeDataのプロパティも必要に応じて増やしてください。
// ui/global/app_themes.dart
enum AppTheme {
GreenLight,
GreenDark,
BlueLight,
BlueDark,
}
// 4つのTheeDataを作成する
final appThemeData = {
AppTheme.GreenLight: ThemeData(
brightness: Brightness.light,
primaryColor: Colors.green,
),
// 省略
}
ThemeDataを切り替えるBlocを作成する
theme_event.dart
// 抜粋
// 注: 抽象クラスThemeEventはEquatableをextendsしている
// 4つのAppTemeのうちで選択されたものをBlocに通知する動きをする
class ThemeChanged extends ThemeEvent {
final AppTheme theme;
ThemeChanged({
@required this.theme,
}) : super([theme]);
}
theme_state.dart
// 抜粋
// 選択されたAppThemeに基づくThemeDataをUIに通知する動きをする
@immutable
class ThemeState extends Equatable {
final ThemeData themeData;
ThemeState({
@required this.themeData,
}) : super([themeData]);
}
theme_bloc.dart
// 抜粋
class ThemeBloc extends Bloc<ThemeEvent, ThemeState> {
ThemeBloc(ThemeState initialState) : super(initialState);
@override
Stream<ThemeState> mapEventToState(
ThemeEvent event,
) async* {
if (event is ThemeChanged) {
// Eventで受け取ったAppThemeに基づくThemeDataを通知する
yield ThemeState(themeData: appThemeData[event.theme]);
}
}
}
ThemeDataを切り替えるButtonを押した際にEventをBlocに送る
preference_page.dart
// 抜粋
return Card(
color: appThemeData[itemAppTheme].primaryColor,
child: ListTile(
title: Text(
itemAppTheme.toString(),
style: appThemeData[itemAppTheme].textTheme.bodyText2,
),
onTap: () {
// 選択されたAppThemeをBlocに流す
//
BlocProvider.of<ThemeBloc>(context).add(
ThemeChanged(theme: itemAppTheme),
);
},
),
);
MaterialAppのthemeプロパティがStateのThemeDataを参照する
main.dart
// 抜粋
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// BlocProviderでThemeBlocをWidget tree挿入する
return BlocProvider(
create: (BuildContext context) => ThemeBloc(
// 初期値を設定する
ThemeState(themeData: appThemeData[AppTheme.GreenLight]),
),
child: BlocBuilder<ThemeBloc, ThemeState>(
builder: _buildWithTheme,
),
);
}
Widget _buildWithTheme(BuildContext context, ThemeState state) {
return MaterialApp(
title: 'Material App',
// themeとしてStateのthemeDataを反映させる
theme: state.themeData,
home: HomePage(),
);
}
}
home_page.dart
// 抜粋
body: Center(
child: Container(
child: Text(
'Home',
// styleとしてBlocから通知されたStateのthemeを呼び出す
style: Theme.of(context).textTheme.headline4,
),
),
),
まとめ
flutter_blocを用いた状態管理によりThemeDataを変更しました。
完成したコードはこちらのresocoderさんの記事にありますので参照してください。
resocoderさんのコードではThemeDataは永続化されませんので、必要に応じて永続化をしてください。記事ではpreferences、SEMBAST、Moor等を勧めています。
またresocoderさんの記事ではflutter_blocのバージョンが古いものになっています。
私の書いた記事はflutter_bloc6.1.1を用いて書いたものになります。適宜バージョンにあった形に直してお使いください。