この記事では、Flutterのテーマ設定themeについて書いていきます。
themeとは
アプリの色(ColorScheme)やテキストの大きさ・フォント(TextTheme)・ボタンの形状を統一し長々と色などを指定する手間を省くことができるウィジェットです。
ThemeDataクラスを利用しMaterialAppにデータを渡します。Themeを理解し正しく配置することによってアプリのライトモード・ダークモードのデザインを決めることができます。
Material3の配色
以下によく使用されるMaterialDesain3の大枠のカテゴリーをまとめました。
Primaryの4つの色役割
| プロパティ名 | 役割 |
|---|---|
| primary | アプリの主色(AppBar、メインボタン、強調アイコンなど) |
| onPrimary | primary の上に配置する文字やアイコンの色(読みやすいコントラストが自動選択) |
| primaryContainer | カードやコンテナなど、面で主張したいときの背景色 |
| onPrimaryContainer | primaryContainer の上に配置する文字やアイコンの色 |
Secondaryの4つの色役割
| プロパティ名 | 役割 |
|---|---|
| secondary | 補助的なUI要素(フィルタチップ、選択状態のハイライトなど) |
| onSecondary | secondary の上に配置する文字やアイコンの色 |
| secondaryContainer | Primaryより目立たせたくない面やカード、FABの背景色 |
| onSecondaryContainer | secondaryContainer の上に配置する文字やアイコンの色 |
Errorの4つの色役割
| プロパティ名 | 役割 | 主な使用場所 |
|---|---|---|
| error | エラーや危険な状態を示す強調色 | 入力エラー枠、削除ボタン、エラー用アイコンなど |
| onError | error の上に配置する文字・アイコン色 | error 色のボタン上の文字・アイコン |
| errorContainer | エラーメッセージなどの背景面(少し控えめな赤系) | エラーアラートの背景、警告カードの面 |
| onErrorContainer | errorContainer の上に配置する文字・アイコン色 | errorContainer 内の解説テキスト |
Surfaceの4つの色役割
| プロパティ名 | 役割 | 主な使用場所 |
|---|---|---|
| surface | 画面全体の基本となる背景色 | Scaffold の背景、ダイアログのベース面 |
| onSurface | surface の上に配置する基本テキスト・アイコン色 | メインの文章、見出し、アクティブなアイコン |
| surfaceContainer | 背景と区別して浮かせるカードやエリアの面 | Card の背景、ボトムシート、リスト項目 |
| onSurfaceVariant | 少し目立たせたくない(補足的な)テキスト・アイコン色 | サブタイトル |
要約するとメリットは
- 色の管理
- テキストスタイル
- 個々のパーツ設定
になります。
主要なプロパティ
| プロパティ | 説明 |
|---|---|
colorScheme | アプリ全体の配色を定義 |
textTheme | テキストのスタイル(見出し、本文など) |
appBarTheme | AppBarの外観 |
elevatedButtonTheme | ElevatedButtonのスタイル |
inputDecorationTheme | TextFieldなどの入力フィールドの外観 |
子ウィジェットからよびだす
子ウィジェットからよびだすには、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),
],
),
),
);
}
}

