Flutterのテーマ設定theme

Flutter/Dart

この記事では、Flutterのテーマ設定themeについて書いていきます。

themeとは

アプリの色(ColorScheme)やテキストの大きさ・フォント(TextTheme)・ボタンの形状を統一し長々と色などを指定する手間を省くことができるウィジェットです。

ThemeDataクラスを利用しMaterialAppにデータを渡します。Themeを理解し正しく配置することによってアプリのライトモード・ダークモードのデザインを決めることができます。

Material3の配色

以下によく使用されるMaterialDesain3の大枠のカテゴリーをまとめました。

Primaryの4つの色役割

プロパティ名役割
primaryアプリの主色(AppBar、メインボタン、強調アイコンなど)
onPrimaryprimary の上に配置する文字やアイコンの色(読みやすいコントラストが自動選択)
primaryContainerカードやコンテナなど、面で主張したいときの背景色
onPrimaryContainerprimaryContainer の上に配置する文字やアイコンの色

Secondaryの4つの色役割

プロパティ名役割
secondary補助的なUI要素(フィルタチップ、選択状態のハイライトなど)
onSecondarysecondary の上に配置する文字やアイコンの色
secondaryContainerPrimaryより目立たせたくない面やカード、FABの背景色
onSecondaryContainersecondaryContainer の上に配置する文字やアイコンの色

Errorの4つの色役割

プロパティ名役割主な使用場所
errorエラーや危険な状態を示す強調色入力エラー枠、削除ボタン、エラー用アイコンなど
onErrorerror の上に配置する文字・アイコン色error 色のボタン上の文字・アイコン
errorContainerエラーメッセージなどの背景面(少し控えめな赤系)エラーアラートの背景、警告カードの面
onErrorContainererrorContainer の上に配置する文字・アイコン色errorContainer 内の解説テキスト

Surfaceの4つの色役割

プロパティ名役割主な使用場所
surface画面全体の基本となる背景色Scaffold の背景、ダイアログのベース面
onSurfacesurface の上に配置する基本テキスト・アイコン色メインの文章、見出し、アクティブなアイコン
surfaceContainer背景と区別して浮かせるカードやエリアの面Card の背景、ボトムシート、リスト項目
onSurfaceVariant少し目立たせたくない(補足的な)テキスト・アイコン色サブタイトル

要約するとメリットは

  • 色の管理
  • テキストスタイル
  • 個々のパーツ設定

になります。

主要なプロパティ

プロパティ説明
colorSchemeアプリ全体の配色を定義
textThemeテキストのスタイル(見出し、本文など)
appBarThemeAppBarの外観
elevatedButtonThemeElevatedButtonのスタイル
inputDecorationThemeTextFieldなどの入力フィールドの外観

子ウィジェットからよびだす

子ウィジェットからよびだすには、Theme.of(context)を使用し、画面のWidget内でThemeで定めた色や文字スタイルを使用します。

Widget build(BuildContext context) {
  // 現在のThemeDataを取得
  final theme = Theme.of(context);
  final colorScheme = theme.colorScheme;
  final textTheme = theme.textTheme;

  return Scaffold(
    backgroundColor: colorScheme.surface,
    body: Text(
      'テーマを使用したテキスト',
      style: textTheme.headlineLarge?.copyWith(
        color: colorScheme.primary, // 既存スタイルの一部だけ変更したい時
      ),
    ),
  );
}

特定の場所だけ色を変えたい

特定の場所だけ色を変えたい場合、ウィジェットを局所的にthemeでつつみこみます。

Theme(
  // 親のThemeをコピーしつつ、一部だけ上書きする
  data: Theme.of(context).copyWith(
    colorScheme: Theme.of(context).colorScheme.copyWith(
      primary: Colors.red,
    ),
  ),
  child: const CustomCard(),
)

サンプル

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(
      debugShowCheckedModeBanner: false,

      // --- ここでアプリ全体の色(テーマ)を決める ---
      theme: ThemeData(
        // ベースとなる色を1つ指定するだけで、アプリ全体の色が自動で統一される
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange),
      ),

      home: const MainScreen(),
    );
  }
}

class MainScreen extends StatelessWidget {
  const MainScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // theme で決めたテーマカラーを呼び出して背景色にする
        backgroundColor: Theme.of(context).colorScheme.primaryContainer,
        title: const Text('Theme の基本デモ'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {},
          child: const Text('ボタンも自動でテーマ色になる'),
        ),
      ),
    );
  }
}

サンプル

TextThereを使ったサンプルです。

サンプルは13行目です。

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(
      // アプリ全体のテキストスタイルを定義
      theme: ThemeData(
        textTheme: const TextTheme(
          headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
          bodyMedium: TextStyle(fontSize: 16, color: Colors.black87),
        ),
      ),
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    // テーマから textTheme を取得
    final textTheme = Theme.of(context).textTheme;

    return Scaffold(
      appBar: AppBar(title: const Text('TextTheme サンプル')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // headlineLarge(大見出し)を適用
            Text('大見出し', style: textTheme.headlineLarge),

            const SizedBox(height: 12),

            // bodyMedium(標準の本文)を適用
            Text('これは標準サイズの本文テキストです。', style: textTheme.bodyMedium),
          ],
        ),
      ),
    );
  }
}
タイトルとURLをコピーしました