FlutterでThemeを設定する方法
Theme(テーマ)の簡単な説明です。ダークモードに対応する方法も紹介します。
目次
Theme(テーマ)とは
Themeはテーマです。とっても難しい発音です。
Themeがあることでアプリにデザインの統一感が生まれます。
Flutterの標準テーマは、Material3です。
環境
- Windows11 23H2
- Flutter 3.24.1
- Dart 3.5.1
- User Level: FlutterのカウンターAppをエミュレータで確認できる
Themeの設定方法
Themeには、ライトモード用とダークモード用の2種類があります。
これらはMaterialAppのthemeプロパティとdarkThemeプロパティにそれぞれ設定します。
themeプロパティにはライトモードのテーマを、darkThemeプロパティにはダークモードのテーマを設定します。
以下のサンプルコードを参考に、MaterialAppのプロパティを編集してください。
これが最もシンプルな実装例です。
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Theme Demo',
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: const MyHomePage(),
);
}
以下は画面の例です。
サンプルWidgetとして、Text、FlutterLogo、Icon、Button、FAB (FloatingActionButton)、を設置しました。
デフォルトのカラーは”deepPurple”のようですね。青っぽい紫なのかな。
Darkモードの確認
FlutterのWidgetは、Darkモードに対応しています。
Darkモードへ移行すればDarkThemeになるはずです。確認してみましょう。
確認方法2あります。
1.OSの設定をDarkモードに変更する
以下は、Androidの例です。
設定 > Display > Dark theme
Darkモードに移行したアプリの画面を確認してみましょう。
DarkThemeに切り替わりましたね。
Textの文字、Elevated Buttonの背景や文字の色が変わっています。
ハートのアイコンやFABの色も変わっています。
FlutterLogoは、文字色は変化していますが、Logoは変わっていないようですね。
2.themeModeを設定する
Flutterアプリはテーマモードを任意に指定できます。
darkモードを指定してみましょう。
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Theme Demo',
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.dark, // <--- ここを追加
home: const MyHomePage(),
);
}
使用できる値は以下です。
- ThemeMode.dark
- ThemeMode.light
- ThemeMode.system …default
Memo
VSCodeであれば、「ThemeMode.」まで入力後、Ctrl+Spaceで候補が表示されますね。
Darkモードの確認方法まとめ
OSの設定を変更するか、themeModeを変更することでダークモードを確認できます。
テーマカラーを変更する
さて、LightテーマとDarkテーマの設定や、Darkモードの確認方法もわかりました。
次は、テーマカラーを変更してましょう。ブランドカラーのようなもですね。
先ほどの例ですと青っぽい紫色がデフォルトのテーマカラーのようでしたね。
変更してみましょう。
seedColorの変更
配色は、colorSchemeの担当です。
ThemeDataのcolorSchemeプロパティにColorSchemeインスタンスを割り当てましょう。
ここで、fromSeedコンストラクタにseedColorを与えます。色の種という意味ですね。seedColorを元に色のパレットのような物が生成されます。
colorSchemeの変更は、themeとdarkThemeの両方に設定する必要があります。
また、brightnessも設定しますのでお忘れなく。
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Theme Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.light,
),
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
),
),
// themeMode: ThemeMode.dark,
home: const MyHomePage(),
);
}
以下画面です。全体が青っぽくなりました。
ちょっとわかり難いですが、Lightモードの背景色もうっすら変わっています。とても薄い青色に変わりました。
テーマカラーの設定方法も簡単でしたね。
seedColorを変更しても、アプリ全体の配色が統一感を失うことなく変わりました。
seedColorが、deepPurpleからblueに変わり、色のパレットが生成され、各Widgetの色が変わったのです。
Color roles
Color roleをちょっとだけ紹介します。
Color role とは、色のパレットのことです。
そのパレットには名前が付いていて、使いどころもそこそこ決まっています。
「on<role>」が付く名称は、その<role>上で使用されるものです。コントラストが維持されるので認識しやすい配色になっています。
- Primary …強調表示するとき。
- On primary …primary上のテキストやアイコンなど。
- Primary container …主要コンポーネントの表面。
- On primary container …Primary container 上のテキストやアイコンなど。
- Secondary …二次
- Tertiary …三次
- Surface …表面
- などなど
ColorScheme.fromSeed名前付きコンストラクタは、seedColorを元に決められたアルゴリズムによって Color role を生成します。
widgetに色を与える場合、Color role を使います。
Color role を使うことで、アプリ全体の統一感を維持できます。
今回は、ハートアイコンの色指定にPromary を使用しています。
// アイコンにprimaryColorを適用
Icon(
Icons.favorite,
size: 32.0,
color: Theme.of(context).colorScheme.primary,
),
Color Role を使用することで、seedColor変更時、Darkモード移行時に、自動で適切な色に変わってくれます。
Memo
標準WidgetがColor roleを使用しているかどうかはわかりません。例えば、AppBarは内容物がスクロールすると少し暗い背景色になりますが、どのRoleの色にも一致しませんでした。SurfaceContainerの色が一番似ていたかな?
Memo
学習中に、primarySwatch の説明に出会うことがあると思います。primarySwatchは Material2で使用されていたものです。
Code
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Theme Demo',
//
// theme: ThemeData.light(),
// darkTheme: ThemeData.dark(),
//
// Light Theme
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.light,
),
useMaterial3: true,
),
//
// Dark Theme
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
),
useMaterial3: true,
),
//
// themeMode: ThemeMode.system, // default
// themeMode: ThemeMode.light,
// themeMode: ThemeMode.dark, // <--- ここを弄るとテーマが変わる
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Theme Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
//
// Text
Text(
'これはテーマのデモです',
style: Theme.of(context).textTheme.headlineMedium,
),
//
// Flutter Logo
FlutterLogo(
size: 120,
textColor: Theme.of(context).colorScheme.primary,
style: FlutterLogoStyle.stacked,
curve: Curves.bounceOut,
duration: const Duration(seconds: 1),
),
//
// アイコンにprimaryColorを適用
Icon(
Icons.favorite,
size: 32.0,
color: Theme.of(context).colorScheme.primary,
),
//
// Buttons
ElevatedButton(
onPressed: () {},
child: const Text('Elevated Button'),
),
//
TextButton(
onPressed: () {},
child: const Text('Text Button'),
),
//
OutlinedButton(
onPressed: () {},
child: const Text('Outlined Button'),
),
//
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: '',
child: const Icon(Icons.add),
),
);
}
}
関連記事
以上