主题(Theme)
主题是 Flutter 应用的视觉灵魂——它定义了全局的颜色、字体、形状、间距等样式规范。用好主题,你可以:
- 一处配置,全局生效:改一个颜色,所有按钮跟着变
- 轻松支持深色模式:亮色/暗色两套主题自动切换
- 保持 UI 一致性:避免每个页面手动写颜色值
核心概念:ThemeData
ThemeData 是 Flutter 主题的数据对象,包含了应用所有视觉配置。它在 MaterialApp 中设置后,整个应用都可以通过 Theme.of(context) 获取。
最简配置
MaterialApp(
theme: ThemeData(
colorSchemeSeed: Colors.blue, // Material 3:一个种子颜色生成完整方案
useMaterial3: true, // 启用 Material 3
),
)只需要两行,Flutter 就会根据 Colors.blue 自动生成一套完整的色彩方案,包括 primary、secondary、surface、error 等所有颜色,以及它们的亮色和暗色变体。
在 MaterialApp 中设置主题
MaterialApp 提供三个主题相关参数:
MaterialApp(
theme: ThemeData(...), // ① 亮色主题(必需)
darkTheme: ThemeData(...), // ② 暗色主题(可选)
themeMode: ThemeMode.system, // ③ 主题模式:跟随系统/强制亮色/强制暗色
)| 参数 | 作用 | 何时生效 |
|---|---|---|
theme | 亮色主题 | 始终作为默认主题 |
darkTheme | 暗色主题 | 当 themeMode 为 dark 或 system(系统为暗色)时生效 |
themeMode | 主题切换策略 | 决定使用 theme 还是 darkTheme |
三者的关系
- 如果只设
theme,不设darkTheme,则无论系统是亮色还是暗色,都使用theme - 如果同时设了
theme和darkTheme,themeMode决定使用哪一套 themeMode默认为ThemeMode.system(跟随系统)
Material 3 的 colorSchemeSeed
Material 3 引入了种子颜色机制——你只需要指定一个颜色,Flutter 会自动生成包含 20+ 种颜色的完整色彩方案。这是 Material 3 推荐的配色方式。
ThemeData(
colorSchemeSeed: Colors.blue, // 一行代码,生成完整色彩方案
useMaterial3: true,
)生成的颜色及其用途
| 颜色 | 用途 | 说明 |
|---|---|---|
primary | 主要按钮、AppBar、Tab 选中色、FAB | 应用最核心的品牌色 |
onPrimary | primary 背景上的文字/图标颜色 | 保证在 primary 上可读 |
primaryContainer | primary 的浅色变体 | 用于次要的强调区域 |
onPrimaryContainer | primaryContainer 上的文字颜色 | — |
secondary | 次要操作、筛选标签 | 辅助强调色 |
onSecondary | secondary 上的文字颜色 | — |
surface | 卡片、对话框、Scaffold 背景 | 页面底色 |
onSurface | surface 上的文字颜色 | 正文文字默认色 |
surfaceVariant | 与 surface 形成对比的表面色 | 用于区分不同区域 |
error | 错误状态、输入校验失败 | 红色系 |
onError | error 上的文字颜色 | — |
outline | 边框、分割线 | 微弱视觉分隔 |
"on" 前缀的含义
onXxx 表示「在 Xxx 颜色背景上使用的颜色」。例如 onPrimary 就是在 primary 背景上的文字颜色。这种命名方式保证了前景/背景的配对关系清晰,不会出现「白字白底」的问题。
常用组件主题配置
ThemeData 可以统一配置各类组件的样式,避免在每个使用处重复写样式:
ThemeData(
colorSchemeSeed: Colors.blue,
useMaterial3: true,
// ---------- AppBar ----------
appBarTheme: const AppBarTheme(
centerTitle: true,
// Material 3 下颜色由 colorScheme 自动决定
// 如需去除表面着色效果:
surfaceTintColor: Colors.transparent,
),
// ---------- 卡片 ----------
cardTheme: CardTheme(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
// ---------- ElevatedButton ----------
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 48),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
),
// ---------- 输入框 ----------
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
// ---------- 全局字体 ----------
textTheme: GoogleFonts.notoSansTextTheme(),
)Flutter 3.35 变更
AppBarTheme.backgroundColor 和 AppBarTheme.foregroundColor 已废弃。Material 3 下 AppBar 的颜色默认由 ColorScheme.surface 决定,可通过 surfaceTintColor 微调。如需自定义颜色,推荐使用 colorScheme.copyWith(surface: ...) 或 AppBarTheme(backgroundColor: ...) (仍可用但已废弃)。
在 Widget 中使用主题
设置主题后,在任意 Widget 中都可以通过 Theme.of(context) 获取主题数据:
@override
Widget build(BuildContext context) {
final theme = Theme.of(context); // 获取完整主题
final colorScheme = theme.colorScheme; // 获取色彩方案
final textTheme = theme.textTheme; // 获取文字样式方案
return Container(
color: colorScheme.surface, // 使用主题背景色
child: Text(
'Hello',
style: textTheme.titleLarge?.copyWith( // 使用主题文字样式
color: colorScheme.primary, // 使用主题主色
),
),
);
}Material 3 文字样式
textTheme 提供了语义化的文字样式,不需要手动设置 fontSize 和 fontWeight:
| 样式名 | 用途 | 大约字号 |
|---|---|---|
displayLarge ~ displaySmall | 超大标题 | 57 ~ 36 |
headlineLarge ~ headlineSmall | 页面大标题 | 32 ~ 24 |
titleLarge ~ titleSmall | 卡片/列表标题 | 22 ~ 14 |
bodyLarge ~ bodySmall | 正文 | 16 ~ 12 |
labelLarge ~ labelSmall | 按钮/标签 | 14 ~ 11 |
// ✅ 推荐:使用语义化样式
Text('标题', style: textTheme.titleLarge)
Text('正文', style: textTheme.bodyMedium)
Text('辅助', style: textTheme.bodySmall)
// ❌ 不推荐:手动指定字号(与主题脱节)
Text('标题', style: TextStyle(fontSize: 22))局部覆盖主题
如果只想让某个子树使用不同的主题,可以用 Theme Widget 包裹,配合 copyWith 只修改需要的部分:
Theme(
data: Theme.of(context).copyWith(
// 只修改颜色方案中的 primary
colorScheme: Theme.of(context).colorScheme.copyWith(
primary: Colors.red,
),
),
child: YourWidget(), // 这个子树中 primary 变为红色
)深色/浅色模式适配
深色模式适配是主题最核心的应用场景之一。Flutter 提供了完善的机制来支持亮色和暗色的自动切换。
第一步:同时配置亮色和暗色主题
MaterialApp(
// 亮色主题
theme: ThemeData(
colorSchemeSeed: Colors.blue,
useMaterial3: true,
brightness: Brightness.light, // 可省略,colorSchemeSeed 默认生成亮色方案
),
// 暗色主题
darkTheme: ThemeData(
colorSchemeSeed: Colors.blue,
useMaterial3: true,
brightness: Brightness.dark, // 可省略,colorSchemeSeed + darkTheme 上下文会自动生成暗色方案
),
// 跟随系统
themeMode: ThemeMode.system,
)使用 colorSchemeSeed 时
当使用 colorSchemeSeed 时,Flutter 会根据种子颜色自动生成亮色和暗色两套方案。你只需要分别写在 theme 和 darkTheme 中,不需要手动指定每个暗色颜色值。
第二步:在代码中始终使用主题颜色
这是深色模式适配的关键——代码中绝对不要硬编码颜色值,而是统一从 colorScheme 获取:
// ✅ 正确:使用主题颜色,深色模式自动适配
Container(
color: colorScheme.surface, // 亮色=白色,暗色=深灰色
child: Text(
'Hello',
style: TextStyle(color: colorScheme.onSurface), // 亮色=黑色,暗色=白色
),
)
// ❌ 错误:硬编码颜色,深色模式下白字白底看不见
Container(
color: Colors.white,
child: Text('Hello', style: TextStyle(color: Colors.black)),
)第三步:控制主题切换方式
themeMode 的三个值:
| 值 | 行为 |
|---|---|
ThemeMode.system | 跟随系统设置(默认) |
ThemeMode.light | 始终使用亮色主题 |
ThemeMode.dark | 始终使用暗色主题 |
让用户手动切换深色模式
最常见的做法是在设置页面提供切换开关:
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ThemeMode _themeMode = ThemeMode.system; // 默认跟随系统
void _toggleTheme(bool isDark) {
setState(() {
_themeMode = isDark ? ThemeMode.dark : ThemeMode.light;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(colorSchemeSeed: Colors.blue, useMaterial3: true),
darkTheme: ThemeData(colorSchemeSeed: Colors.blue, useMaterial3: true, brightness: Brightness.dark),
themeMode: _themeMode,
home: HomePage(onThemeToggle: _toggleTheme, themeMode: _themeMode),
);
}
}设置页面中的 Switch:
Switch(
value: themeMode == ThemeMode.dark,
onChanged: (isDark) => onThemeToggle(isDark),
)使用 SharedPreferences 持久化主题选择
用户选择的主题应该在重启后保持。使用 shared_preferences 包:
flutter pub add shared_preferencesclass _MyAppState extends State<MyApp> {
ThemeMode _themeMode = ThemeMode.system;
@override
void initState() {
super.initState();
_loadThemeMode();
}
Future<void> _loadThemeMode() async {
final prefs = await SharedPreferences.getInstance();
final savedMode = prefs.getString('theme_mode') ?? 'system';
setState(() {
_themeMode = ThemeMode.values.firstWhere(
(e) => e.name == savedMode,
orElse: () => ThemeMode.system,
);
});
}
Future<void> _setThemeMode(ThemeMode mode) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('theme_mode', mode.name);
setState(() => _themeMode = mode);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(colorSchemeSeed: Colors.blue, useMaterial3: true),
darkTheme: ThemeData(colorSchemeSeed: Colors.blue, useMaterial3: true, brightness: Brightness.dark),
themeMode: _themeMode,
home: const HomePage(),
);
}
}在代码中判断当前是深色还是浅色模式
// 方式 1:通过 colorScheme 判断(推荐)
final isDark = Theme.of(context).colorScheme.brightness == Brightness.dark;
// 方式 2:通过 MediaQuery 判断(反映系统设置)
final isDark = MediaQuery.of(context).platformBrightness == Brightness.dark;两种方式的区别
Theme.of(context).colorScheme.brightness:反映的是应用当前实际使用的主题,考虑了themeMode的设置MediaQuery.of(context).platformBrightness:反映的是系统的亮度模式,不受themeMode影响
大多数情况下用方式 1,因为你需要知道的是「应用当前显示的是亮色还是暗色」。
需要特殊处理的场景
有些场景不能仅靠 colorScheme 自动适配,需要手动处理:
图片资源适配
如果应用中有需要区分亮/暗色的图片(如 logo),可以根据主题切换:
Image.asset(
Theme.of(context).colorScheme.brightness == Brightness.dark
? 'assets/logo_dark.png'
: 'assets/logo_light.png',
)自定义颜色不在 colorScheme 中
如果应用有额外的品牌色(如渐变色),需要根据亮度手动选择:
final isDark = Theme.of(context).colorScheme.brightness == Brightness.dark;
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: isDark
? [Colors.blue.shade800, Colors.purple.shade800] // 暗色渐变
: [Colors.blue.shade400, Colors.purple.shade400], // 亮色渐变
),
),
)自定义 ColorScheme 扩展
如果需要很多自定义颜色,可以创建扩展类统一管理:
@immutable
class AppColors extends ThemeExtension<AppColors> {
const AppColors({
required this.brandGradientStart,
required this.brandGradientEnd,
required this.success,
});
final Color brandGradientStart;
final Color brandGradientEnd;
final Color success;
@override
AppColors copyWith({
Color? brandGradientStart,
Color? brandGradientEnd,
Color? success,
}) {
return AppColors(
brandGradientStart: brandGradientStart ?? this.brandGradientStart,
brandGradientEnd: brandGradientEnd ?? this.brandGradientEnd,
success: success ?? this.success,
);
}
@override
AppColors lerp(covariant AppColors? other, double t) {
if (other == null) return this;
return AppColors(
brandGradientStart: Color.lerp(brandGradientStart, other.brandGradientStart, t)!,
brandGradientEnd: Color.lerp(brandGradientEnd, other.brandGradientEnd, t)!,
success: Color.lerp(success, other.success, t)!,
);
}
}在主题中注册:
theme: ThemeData(
colorSchemeSeed: Colors.blue,
useMaterial3: true,
extensions: const [
AppColors(
brandGradientStart: Colors.blue,
brandGradientEnd: Colors.purple,
success: Colors.green,
),
],
),
darkTheme: ThemeData(
colorSchemeSeed: Colors.blue,
useMaterial3: true,
brightness: Brightness.dark,
extensions: const [
AppColors(
brandGradientStart: Colors.blue.shade800,
brandGradientEnd: Colors.purple.shade800,
success: Colors.green.shade700,
),
],
),在 Widget 中使用:
final appColors = Theme.of(context).extension<AppColors>()!;
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [appColors.brandGradientStart, appColors.brandGradientEnd],
),
),
)深色模式适配检查清单
| 检查项 | 说明 |
|---|---|
所有背景色使用 colorScheme.surface | 不要用 Colors.white |
所有正文色使用 colorScheme.onSurface | 不要用 Colors.black |
所有主色使用 colorScheme.primary | 不要硬编码蓝色 |
| 图片资源是否有暗色版本 | logo 等可能需要两套 |
| 渐变色是否适配 | 手动判断亮度或使用 ThemeExtension |
dividerColor 使用 colorScheme.outline | 不要硬编码灰色 |
自定义完整 ColorScheme
如果 colorSchemeSeed 生成的方案不满足需求,可以手动指定 ColorScheme 的每个颜色:
ThemeData(
colorScheme: const ColorScheme(
brightness: Brightness.light,
primary: Color(0xFF1565C0),
onPrimary: Colors.white,
primaryContainer: Color(0xFF90CAF9),
onPrimaryContainer: Color(0xFF0D47A1),
secondary: Color(0xFF651FFF),
onSecondary: Colors.white,
secondaryContainer: Color(0xFFB388FF),
onSecondaryContainer: Color(0xFF4A148C),
surface: Color(0xFFFFFBFE),
onSurface: Color(0xFF1C1B1F),
error: Color(0xFFB3261E),
onError: Colors.white,
outline: Color(0xFF79747E),
),
useMaterial3: true,
)建议
对于大多数应用,使用 colorSchemeSeed 就足够了。手动定义 ColorScheme 适合对品牌色有严格要求的项目。可以使用 Material Theme Builder 在线工具生成 ColorScheme。
使用 flex_color_scheme 插件(进阶)
如果你觉得手动配置 ThemeData 太繁琐,或者想要更多开箱即用的配色方案,可以使用 flex_color_scheme 插件。它是 Flutter 社区最受欢迎的主题插件(3.2k likes),纯 Dart 实现,支持鸿蒙等所有平台。
原生写法 vs flex_color_scheme
| 对比项 | 原生 ThemeData | flex_color_scheme |
|---|---|---|
| 额外依赖 | 无 | 需安装插件 |
| 内置配色方案 | 无(需手动指定种子颜色) | 66 套精美方案 |
| 深色模式 | 需分别写 theme 和 darkTheme | 一行自动生成亮/暗两套 |
| 组件主题圆角 | 需逐个配置 ShapeBorder | 一个属性统一控制 |
| 可视化配置工具 | 无 | Themes Playground 在线工具 |
| Material 3 支持 | ✅ | ✅ |
| 学习成本 | 低 | 低(API 简洁) |
选择建议:
- 初学者 / 简单项目 → 用原生写法,先掌握原理
- 想快速出效果 / 配色需求多 → 用
flex_color_scheme,省时省力 - 对主题有精细控制需求 → 两者都行,
flex_color_scheme也能深度自定义
安装
flutter pub add flex_color_scheme基础用法
import 'package:flex_color_scheme/flex_color_scheme.dart';
MaterialApp(
// 亮色主题
theme: FlexThemeData.light(scheme: FlexScheme.blue),
// 暗色主题
darkTheme: FlexThemeData.dark(scheme: FlexScheme.blue),
// 跟随系统
themeMode: ThemeMode.system,
)对比原生写法,FlexThemeData.light() 和 FlexThemeData.dark() 自动处理了:
- 亮色和暗色方案的正确生成
- 所有
ThemeData颜色属性与ColorScheme的一致性 - 组件主题的默认样式
内置配色方案
flex_color_scheme 提供了 66 套预设方案,常用示例:
FlexScheme.blue // 经典蓝
FlexScheme.indigo // 靛蓝
FlexScheme.hippieBlue // 迷幻蓝
FlexScheme.aquaBlue // 水蓝
FlexScheme.brandBlue // 品牌蓝
FlexScheme.deepBlue // 深蓝
FlexScheme.sakura // 樱花粉
FlexScheme.mandyRed // 曼迪红
FlexScheme.ocean // 海洋
FlexScheme.mallardGreen // 绿头鸭绿
FlexScheme.gold // 金色
FlexScheme.mango // 芒果橙
FlexScheme.verdunHemlock // 凡尔登绿
FlexScheme.dellGenoa // 戴尔热那亚
FlexScheme.redWine // 红酒
FlexScheme.purpleBrown // 紫棕
FlexScheme.green // 绿色
FlexScheme.greyLaw // 灰法可以在 Themes Playground 在线预览所有方案效果。
自定义种子颜色
如果预设方案都不满足需求,可以用自定义颜色作为种子:
// 方式 1:使用自定义品牌色作为种子
FlexThemeData.light(
scheme: FlexScheme.custom,
colors: const FlexSchemeColor(
primary: Color(0xFF1565C0), // 品牌主色
primaryContainer: Color(0xFF90CAF9),
secondary: Color(0xFF651FFF), // 辅助色
secondaryContainer: Color(0xFFB388FF),
),
)
// 方式 2:从种子颜色自动生成(类似原生的 colorSchemeSeed)
FlexThemeData.light(
scheme: FlexScheme.materialBaseline, // 使用 Material 基线种子生成器
useMaterial3: true,
)常用配置
FlexThemeData.light(
scheme: FlexScheme.blue,
// 统一设置所有组件的圆角(原生写法需要逐个配置 ShapeBorder)
defaultRadius: 12.0,
// 表面混合模式(让 surface 与 primary 有微妙的色彩关联)
surfaceMode: FlexSurfaceMode.highSurfaceLowScaffold,
blendLevel: 10,
// AppBar 样式
appBarStyle: FlexAppBarStyle.primary,
appBarOpacity: 1.0,
// 透明效果
transparentStatusBar: true,
// 启用组件主题(开启后才能用 defaultRadius 等属性)
useMaterial3: true,
subThemesData: const FlexSubThemesData(
defaultRadius: 12.0, // 全局圆角
inputDecoratorRadius: 8.0, // 输入框单独圆角
cardRadius: 12.0, // 卡片圆角
elevatedButtonRadius: 8.0, // 按钮圆角
),
)配合 SharedPreferences 实现主题持久化
与原生写法完全一致,只需把 ThemeData 替换为 FlexThemeData:
class _MyAppState extends State<MyApp> {
ThemeMode _themeMode = ThemeMode.system;
FlexScheme _scheme = FlexScheme.blue;
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: FlexThemeData.light(scheme: _scheme),
darkTheme: FlexThemeData.dark(scheme: _scheme),
themeMode: _themeMode,
home: const HomePage(),
);
}
}Themes Playground 可视化配置工具
flex_color_scheme 提供了一个在线可视化配置工具 Themes Playground,你可以在浏览器中实时调整主题参数,然后把生成的代码复制到项目中:
使用流程:
- 在 Playground 中选择一个预设方案或自定义颜色
- 调整圆角、表面混合、AppBar 样式等参数
- 在代码面板中复制生成的
FlexThemeData代码 - 粘贴到你的
MaterialApp中
建议
即使你最终选择使用原生 ThemeData,也推荐去 Playground 逛一逛——它可以帮助你理解 Material 3 主题的各种配置选项和视觉效果。
速查表
| 场景 | 方案 |
|---|---|
| 最简主题 | ThemeData(colorSchemeSeed: Colors.blue, useMaterial3: true) |
| 使用主题颜色 | Theme.of(context).colorScheme.primary |
| 使用主题字体 | Theme.of(context).textTheme.titleLarge |
| 局部覆盖 | Theme(data: Theme.of(context).copyWith(...), child: ...) |
| 深色模式 | 配置 theme + darkTheme + themeMode |
| 跟随系统 | themeMode: ThemeMode.system |
| 强制亮色 | themeMode: ThemeMode.light |
| 强制暗色 | themeMode: ThemeMode.dark |
| 判断当前模式 | Theme.of(context).colorScheme.brightness |
| 自定义扩展颜色 | ThemeExtension<AppColors> |
| 快速出效果(插件) | FlexThemeData.light(scheme: FlexScheme.blue) |
