Flutter テーマとスタイル
このセクションでは、Flutter のテーマ設定について紹介します。Material Design テーマ、カスタムテーマ、ダークモードなどが含まれます。
ThemeData - テーマの適用
ThemeData を使用すると、アプリのテーマカラー、フォント、スタイルなどを一括で設定できます。
例:アプリテーマの構成
void main() {
runApp(
MaterialApp(
theme: ThemeData(
// メインカラー
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.light,
),
// Material 3 を使用
useMaterial3: true,
// フォント
fontFamily: 'Roboto',
// アプリバーテーマ
appBarTheme: const AppBarTheme(
centerTitle: true,
elevation: 0,
),
// ボタンテーマ
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
),
// 入力フィールドのテーマ
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
filled: true,
),
),
home: const MyHomePage(),
),
);
}
runApp(
MaterialApp(
theme: ThemeData(
// メインカラー
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.light,
),
// Material 3 を使用
useMaterial3: true,
// フォント
fontFamily: 'Roboto',
// アプリバーテーマ
appBarTheme: const AppBarTheme(
centerTitle: true,
elevation: 0,
),
// ボタンテーマ
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
),
// 入力フィールドのテーマ
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
filled: true,
),
),
home: const MyHomePage(),
),
);
}
Theme.of - テーマの使用
Widget 内で Theme.of(context) を使用して現在のテーマを取得します。
例:テーマカラーの使用
class ThemedButton extends StatelessWidget {
const ThemedButton({super.key});
@override
Widget build(BuildContext context) {
// テーマを取得
final theme = Theme.of(context);
return Column(
children: [
// テーマカラーを使用
Container(
color: theme.colorScheme.primary,
padding: const EdgeInsets.all(16),
child: Text(
'メインカラー',
style: TextStyle(color: theme.colorScheme.onPrimary),
),
),
const SizedBox(height: 16),
// サブカラーを使用
Container(
color: theme.colorScheme.secondary,
padding: const EdgeInsets.all(16),
child: Text(
'サブカラー',
style: TextStyle(color: theme.colorScheme.onSecondary),
),
),
const SizedBox(height: 16),
// エラーカラーを使用
Container(
color: theme.colorScheme.error,
padding: const EdgeInsets.all(16),
child: Text(
'エラーカラー',
style: TextStyle(color: theme.colorScheme.onError),
),
),
],
);
}
}
const ThemedButton({super.key});
@override
Widget build(BuildContext context) {
// テーマを取得
final theme = Theme.of(context);
return Column(
children: [
// テーマカラーを使用
Container(
color: theme.colorScheme.primary,
padding: const EdgeInsets.all(16),
child: Text(
'メインカラー',
style: TextStyle(color: theme.colorScheme.onPrimary),
),
),
const SizedBox(height: 16),
// サブカラーを使用
Container(
color: theme.colorScheme.secondary,
padding: const EdgeInsets.all(16),
child: Text(
'サブカラー',
style: TextStyle(color: theme.colorScheme.onSecondary),
),
),
const SizedBox(height: 16),
// エラーカラーを使用
Container(
color: theme.colorScheme.error,
padding: const EdgeInsets.all(16),
child: Text(
'エラーカラー',
style: TextStyle(color: theme.colorScheme.onError),
),
),
],
);
}
}
ダークモード
例:ダークモードのサポート
void main() {
runApp(
MaterialApp(
theme: ThemeData(
// ライトテーマ
brightness: Brightness.light,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
darkTheme: ThemeData(
// ダークテーマ
brightness: Brightness.dark,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
),
),
// 同時に有効にする(オプション)
themeMode: ThemeMode.system, // システム設定に従う
// themeMode: ThemeMode.light // ライトモードを強制
// themeMode: ThemeMode.dark // ダークモードを強制
home: const ThemeTogglePage(),
),
);
}
// テーマの切り替え
class ThemeTogglePage extends StatelessWidget {
const ThemeTogglePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('テーマ切り替え')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('現在のテーマ'),
const SizedBox(height: 20),
// テーマ切り替えボタン
ElevatedButton(
onPressed: () {
final currentMode = Theme.of(context).brightness;
if (currentMode == Brightness.light) {
// ダークに切り替え
Theme.of(context).setThemeMode(ThemeMode.dark);
} else {
// ライトに切り替え
Theme.of(context).setThemeMode(ThemeMode.light);
}
},
child: const Text('テーマを切り替え'),
),
],
),
),
);
}
}
runApp(
MaterialApp(
theme: ThemeData(
// ライトテーマ
brightness: Brightness.light,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
darkTheme: ThemeData(
// ダークテーマ
brightness: Brightness.dark,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
),
),
// 同時に有効にする(オプション)
themeMode: ThemeMode.system, // システム設定に従う
// themeMode: ThemeMode.light // ライトモードを強制
// themeMode: ThemeMode.dark // ダークモードを強制
home: const ThemeTogglePage(),
),
);
}
// テーマの切り替え
class ThemeTogglePage extends StatelessWidget {
const ThemeTogglePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('テーマ切り替え')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('現在のテーマ'),
const SizedBox(height: 20),
// テーマ切り替えボタン
ElevatedButton(
onPressed: () {
final currentMode = Theme.of(context).brightness;
if (currentMode == Brightness.light) {
// ダークに切り替え
Theme.of(context).setThemeMode(ThemeMode.dark);
} else {
// ライトに切り替え
Theme.of(context).setThemeMode(ThemeMode.light);
}
},
child: const Text('テーマを切り替え'),
),
],
),
),
);
}
}
カスタムスタイルの拡張
例:カスタム TextTheme
ThemeData(
textTheme: const TextTheme(
// 表示テキスト(大見出し)
displayLarge: TextStyle(fontSize: 57, fontWeight: FontWeight.w400),
displayMedium: TextStyle(fontSize: 45, fontWeight: FontWeight.w400),
displaySmall: TextStyle(fontSize: 36, fontWeight: FontWeight.w400),
// 見出しテキスト
headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.w400),
headlineMedium: TextStyle(fontSize: 28, fontWeight: FontWeight.w400),
headlineSmall: TextStyle(fontSize: 24, fontWeight: FontWeight.w400),
// 見出しテキスト
titleLarge: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
titleMedium: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
titleSmall: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
// 本文テキスト
bodyLarge: TextStyle(fontSize: 16, fontWeight: FontWeight.w400),
bodyMedium: TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
bodySmall: TextStyle(fontSize: 12, fontWeight: FontWeight.w400),
// ラベルテキスト
labelLarge: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
labelMedium: TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
labelSmall: TextStyle(fontSize: 11, fontWeight: FontWeight.w500),
),
)
textTheme: const TextTheme(
// 表示テキスト(大見出し)
displayLarge: TextStyle(fontSize: 57, fontWeight: FontWeight.w400),
displayMedium: TextStyle(fontSize: 45, fontWeight: FontWeight.w400),
displaySmall: TextStyle(fontSize: 36, fontWeight: FontWeight.w400),
// 見出しテキスト
headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.w400),
headlineMedium: TextStyle(fontSize: 28, fontWeight: FontWeight.w400),
headlineSmall: TextStyle(fontSize: 24, fontWeight: FontWeight.w400),
// 見出しテキスト
titleLarge: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
titleMedium: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
titleSmall: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
// 本文テキスト
bodyLarge: TextStyle(fontSize: 16, fontWeight: FontWeight.w400),
bodyMedium: TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
bodySmall: TextStyle(fontSize: 12, fontWeight: FontWeight.w400),
// ラベルテキスト
labelLarge: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
labelMedium: TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
labelSmall: TextStyle(fontSize: 11, fontWeight: FontWeight.w500),
),
)
その他の拡張ThemeData を使用してアプリのスタイルを一元的に管理すると、テーマの切り替えやスタイルの統一を簡単に実現でき、保守もより便利になります。