JINMUSOFTWARE

FlutterでThemeを設定する方法

Flutter

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”のようですね。青っぽい紫なのかな。

Light theme
Light theme

Darkモードの確認

FlutterのWidgetは、Darkモードに対応しています。

Darkモードへ移行すればDarkThemeになるはずです。確認してみましょう。

確認方法2あります。

1.OSの設定をDarkモードに変更する

以下は、Androidの例です。

設定 > Display > Dark theme

Dark mode
Dark mode

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モードの背景色もうっすら変わっています。とても薄い青色に変わりました。

Light theme blue
Light theme blue
Dark theme blue
Dark theme blue

テーマカラーの設定方法も簡単でしたね。

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),
      ),
    );
  }
}

以上