Material3 ThemeData攻略[ダークモード対応・コード有り]
Flutter(Dart)でmaterial3で、ThemeDataを使ったUIデザインの構築を見ていきます。
ダークモードにも対応します。
こんな人におすすめ
・Flutterでダークモード対応をしたい
・material3の、seedColorの使い方を知らない
・文字をアプリで統一したい
・Widgetのデザインをアプリで統一したい
⭐️ 上記がThemeDataを正しく使うことで、簡単に実装できます。
ThemeDataのコードをざっくり見る
今回、説明で使うコードは以下です。
以下では、こちらのコードの詳細を追っていきます。
return MaterialApp(
title: 'Theme Tips',
theme: themeLight(),
darkTheme: themeDark(),
themeMode: themeMode,
home: const Home(),
);
// themeDarkも同様に実装してください。
ThemeData themeLight() {
const primary = Color.fromARGB(255, 56, 106, 31);
// テーマの基本設定
final base = ThemeData(
fontFamily: GoogleFonts.kiwiMaru().fontFamily,
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
brightness: Brightness.light,
seedColor: primary,
primary: primary,
surface: primary,
error: Colors.blue,
surfaceContainerLow: Colors.orange,
),
textTheme: const TextTheme(
bodyMedium: TextStyle(fontSize: 18),
headlineMedium: TextStyle(fontSize: 19, fontWeight: FontWeight.bold),
),
);
return base.copyWith(
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.purple,
foregroundColor: Colors.white,
),
),
floatingActionButtonTheme: base.floatingActionButtonTheme.copyWith(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
),
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
},
),
// scaffoldBackgroundColor: Colors.green,
);
}
画面のコード
画面のコード
class Home extends ConsumerWidget {
const Home({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: Text("Theme Tips",
style: Theme.of(context).textTheme.headlineMedium),
),
body: SingleChildScrollView(
child: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// themeの切り替え
const SettingsSwitchTile(
title: "theme切替え",
),
Text("・seedColorによる自動生成色が適応される例",
style: Theme.of(context).textTheme.headlineMedium),
SizedBox(height: 16),
Text("secoundary",
style: TextStyle(
color: Theme.of(context).colorScheme.secondary)),
Text("tertiary",
style: TextStyle(
color: Theme.of(context).colorScheme.tertiary)),
Text("onSurface",
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface)),
Text("errorContainer",
style: TextStyle(
color: Theme.of(context).colorScheme.errorContainer)),
SizedBox(height: 16),
Text("・ColorSchemeのカスタマイズが適応される例",
style: Theme.of(context).textTheme.headlineMedium),
SizedBox(height: 16),
const TextField(
decoration: InputDecoration(
labelText: "Error 色を青にカスタマイズ",
hintText: "Input something",
errorText: "Error Message",
),
),
const SizedBox(height: 16),
const Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: const Text("surfaceContainerLowをオレンジにカスタマイズ"),
),
),
SizedBox(height: 16),
/// 以下は、app.dart で設定したテーマが適応される例
///
/// error が適応される例
Text("・ウィジェットのカスタマイズが適応される例",
style: Theme.of(context).textTheme.headlineMedium),
SizedBox(height: 16),
const SizedBox(height: 16),
// elevatedButtonの設定が適応される例
ElevatedButton(
onPressed: () {},
child: const Text("ElevatedButtonカスタマイズ"),
),
// FloatingActionButtonの設定が適応される例
FloatingActionButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Dialog Title"),
content: const Text("This is a simple dialog."),
actions: <Widget>[
TextButton(
child: const Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
},
// floatingActionButtonの設定が適応される例
child: Text("FAB"),
),
],
),
),
),
),
);
}
}
class SettingsSwitchTile extends ConsumerStatefulWidget {
final String title;
const SettingsSwitchTile({
Key? key,
required this.title,
}) : super(key: key);
@override
_SettingsSwitchTileState createState() => _SettingsSwitchTileState();
}
class _SettingsSwitchTileState extends ConsumerState<SettingsSwitchTile> {
@override
Widget build(BuildContext context) {
final themeNotifier = ref.read(themeColorRepositoryProvider.notifier);
return ListTile(
contentPadding:
const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
title: Text(widget.title),
trailing: Switch(
autofocus: true,
value: Theme.of(context).brightness == Brightness.dark,
onChanged: (bool newValue) {
themeNotifier.setState(
newValue ? ThemeMode.dark : ThemeMode.light,
);
},
),
);
}
}
カスタムフォントを適応する
以下で、全体のカスタムフォントが適応できます。
ちなみに、kiwiMaruかわいいのでおすすめです。
final base = ThemeData(
/// アプリのフォントを指定
fontFamily: GoogleFonts.kiwiMaru().fontFamily,
seedColorを理解する
まず、初めに、ColorSchemeを設定する場合、必須のプロパティである seedColorを理解する必要があります。
これは、一言でいうと、基準となる色を設定して関連する色を自動で配色する仕組みです。
以下の表をご覧ください。
// 以下のprimay color (緑)
const primary = Color.fromARGB(255, 56, 106, 31);
...
seedColor: primary,
以下のように、色が生成されていることがわかります。
自動で配色された色をカスタマイズしたい場合
例えば、極端ですが、上記の表では、errorの色は赤ですよね。
こちら、青に設定するには、どうすればいいでしょうか。
カスタマイズしてみましょう。
colorScheme: ColorScheme.fromSeed(
brightness: Brightness.light,
...
error: Colors.blue, ← ここで青に設定する。
適応される色を、ざっくり知りたい場合は以下の記事をご参照ください。
※backgroudなど、現在は使われていないものもあります。
Widgetのコード内部を見て、色を決定する。
上記で、完全に色をコントロールするのは正直難しいです。
ですので、コードの内部を見ていく必要があります。
以下は、Cardのbackgroudがどうなっているかみてみましょう。
/// The card's background color.
///
/// Defines the card's [Material.color].
///
/// If this property is null then the ambient [CardTheme.color] is used. If that is null,
/// and [ThemeData.useMaterial3] is true, then [ColorScheme.surfaceContainerLow] of
/// [ThemeData.colorScheme] is used. Otherwise, [ThemeData.cardColor] is used.
final Color? color;
背景色が null の場合、以下の順序で色が適用されます。
カードテーマの color プロパティ。
ThemeData.useMaterial3 が true の場合、ColorScheme.surfaceContainerLow の色。
上記が全て null の場合、ThemeData.cardColor が使われます。
となっていました。 したがって、surfaceContainerLowを設定してあげればいいことになります。
上記の表をご覧ください。なにも設定していない場合は、surfaceContainerLowは薄い緑ですよね。
オレンジにしてみます。
colorScheme: ColorScheme.fromSeed(
...
surfaceContainerLow: Colors.orange,
文字を統一しよう
TextThemeにより文字の統一が可能です。
例えば、ヘッドラインや小見出しなどの文字を統一できます。
以下のような感じです。
※ ちなみに、 Text("") は、bodyMediumが適応されているので、以下で統一可能です。
今回は、大きめにしましたが仕様書とかでは大体14あたりではないでしょうか。
// デフォルトのテキストスタイル
bodyMedium: TextStyle(fontSize: 18),
// ヘッドラインのテキストスタイル
headlineMedium: TextStyle(fontSize: 19, fontWeight: FontWeight.bold),
Widgetに適応されるテーマを設定しよう
以下では、colorSchemeで自動で配色される色ではなく、Widget別にデザインをカスタマイズする例を挙げます。
たとえば、ElevatedButtonのテーマを設定する例です。
背景を紫にして、文字の色を白くしました。
return base.copyWith(
// ElevatedButtonのテーマを設定する例
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.purple,
foregroundColor: Colors.white,
),
),
アプリの背景色を変えよう
scaffoldBackgroundColor: Colors.green,
極端ですが緑にもできます。
まとめ
仕様が決まっていれば、割と簡単にThemeDataを設定できそうですね!
参考になれば幸いです!!
Discussion