Skip to content

主题(Theme)

主题是 Flutter 应用的视觉灵魂——它定义了全局的颜色、字体、形状、间距等样式规范。用好主题,你可以:

  • 一处配置,全局生效:改一个颜色,所有按钮跟着变
  • 轻松支持深色模式:亮色/暗色两套主题自动切换
  • 保持 UI 一致性:避免每个页面手动写颜色值

核心概念:ThemeData

ThemeData 是 Flutter 主题的数据对象,包含了应用所有视觉配置。它在 MaterialApp 中设置后,整个应用都可以通过 Theme.of(context) 获取。

最简配置

dart
MaterialApp(
  theme: ThemeData(
    colorSchemeSeed: Colors.blue,  // Material 3:一个种子颜色生成完整方案
    useMaterial3: true,            // 启用 Material 3
  ),
)

只需要两行,Flutter 就会根据 Colors.blue 自动生成一套完整的色彩方案,包括 primary、secondary、surface、error 等所有颜色,以及它们的亮色和暗色变体。

在 MaterialApp 中设置主题

MaterialApp 提供三个主题相关参数:

dart
MaterialApp(
  theme: ThemeData(...),         // ① 亮色主题(必需)
  darkTheme: ThemeData(...),     // ② 暗色主题(可选)
  themeMode: ThemeMode.system,   // ③ 主题模式:跟随系统/强制亮色/强制暗色
)
参数作用何时生效
theme亮色主题始终作为默认主题
darkTheme暗色主题themeMode 为 dark 或 system(系统为暗色)时生效
themeMode主题切换策略决定使用 theme 还是 darkTheme

三者的关系

  • 如果只设 theme,不设 darkTheme,则无论系统是亮色还是暗色,都使用 theme
  • 如果同时设了 themedarkThemethemeMode 决定使用哪一套
  • themeMode 默认为 ThemeMode.system(跟随系统)

Material 3 的 colorSchemeSeed

Material 3 引入了种子颜色机制——你只需要指定一个颜色,Flutter 会自动生成包含 20+ 种颜色的完整色彩方案。这是 Material 3 推荐的配色方式。

dart
ThemeData(
  colorSchemeSeed: Colors.blue,  // 一行代码,生成完整色彩方案
  useMaterial3: true,
)

生成的颜色及其用途

颜色用途说明
primary主要按钮、AppBar、Tab 选中色、FAB应用最核心的品牌色
onPrimaryprimary 背景上的文字/图标颜色保证在 primary 上可读
primaryContainerprimary 的浅色变体用于次要的强调区域
onPrimaryContainerprimaryContainer 上的文字颜色
secondary次要操作、筛选标签辅助强调色
onSecondarysecondary 上的文字颜色
surface卡片、对话框、Scaffold 背景页面底色
onSurfacesurface 上的文字颜色正文文字默认色
surfaceVariant与 surface 形成对比的表面色用于区分不同区域
error错误状态、输入校验失败红色系
onErrorerror 上的文字颜色
outline边框、分割线微弱视觉分隔

"on" 前缀的含义

onXxx 表示「在 Xxx 颜色背景上使用的颜色」。例如 onPrimary 就是在 primary 背景上的文字颜色。这种命名方式保证了前景/背景的配对关系清晰,不会出现「白字白底」的问题。

常用组件主题配置

ThemeData 可以统一配置各类组件的样式,避免在每个使用处重复写样式:

dart
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.backgroundColorAppBarTheme.foregroundColor 已废弃。Material 3 下 AppBar 的颜色默认由 ColorScheme.surface 决定,可通过 surfaceTintColor 微调。如需自定义颜色,推荐使用 colorScheme.copyWith(surface: ...)AppBarTheme(backgroundColor: ...) (仍可用但已废弃)。

在 Widget 中使用主题

设置主题后,在任意 Widget 中都可以通过 Theme.of(context) 获取主题数据:

dart
@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
dart
// ✅ 推荐:使用语义化样式
Text('标题', style: textTheme.titleLarge)
Text('正文', style: textTheme.bodyMedium)
Text('辅助', style: textTheme.bodySmall)

// ❌ 不推荐:手动指定字号(与主题脱节)
Text('标题', style: TextStyle(fontSize: 22))

局部覆盖主题

如果只想让某个子树使用不同的主题,可以用 Theme Widget 包裹,配合 copyWith 只修改需要的部分:

dart
Theme(
  data: Theme.of(context).copyWith(
    // 只修改颜色方案中的 primary
    colorScheme: Theme.of(context).colorScheme.copyWith(
      primary: Colors.red,
    ),
  ),
  child: YourWidget(),  // 这个子树中 primary 变为红色
)

深色/浅色模式适配

深色模式适配是主题最核心的应用场景之一。Flutter 提供了完善的机制来支持亮色和暗色的自动切换。

第一步:同时配置亮色和暗色主题

dart
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 会根据种子颜色自动生成亮色和暗色两套方案。你只需要分别写在 themedarkTheme 中,不需要手动指定每个暗色颜色值。

第二步:在代码中始终使用主题颜色

这是深色模式适配的关键——代码中绝对不要硬编码颜色值,而是统一从 colorScheme 获取:

dart
// ✅ 正确:使用主题颜色,深色模式自动适配
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始终使用暗色主题

让用户手动切换深色模式

最常见的做法是在设置页面提供切换开关:

dart
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:

dart
Switch(
  value: themeMode == ThemeMode.dark,
  onChanged: (isDark) => onThemeToggle(isDark),
)

使用 SharedPreferences 持久化主题选择

用户选择的主题应该在重启后保持。使用 shared_preferences 包:

bash
flutter pub add shared_preferences
dart
class _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(),
    );
  }
}

在代码中判断当前是深色还是浅色模式

dart
// 方式 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),可以根据主题切换:

dart
Image.asset(
  Theme.of(context).colorScheme.brightness == Brightness.dark
      ? 'assets/logo_dark.png'
      : 'assets/logo_light.png',
)

自定义颜色不在 colorScheme 中

如果应用有额外的品牌色(如渐变色),需要根据亮度手动选择:

dart
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 扩展

如果需要很多自定义颜色,可以创建扩展类统一管理:

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

在主题中注册:

dart
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 中使用:

dart
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 的每个颜色:

dart
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

对比项原生 ThemeDataflex_color_scheme
额外依赖需安装插件
内置配色方案无(需手动指定种子颜色)66 套精美方案
深色模式需分别写 themedarkTheme一行自动生成亮/暗两套
组件主题圆角需逐个配置 ShapeBorder一个属性统一控制
可视化配置工具Themes Playground 在线工具
Material 3 支持
学习成本低(API 简洁)

选择建议:

  • 初学者 / 简单项目 → 用原生写法,先掌握原理
  • 想快速出效果 / 配色需求多 → 用 flex_color_scheme,省时省力
  • 对主题有精细控制需求 → 两者都行,flex_color_scheme 也能深度自定义

安装

bash
flutter pub add flex_color_scheme

基础用法

dart
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 套预设方案,常用示例:

dart
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 在线预览所有方案效果。

自定义种子颜色

如果预设方案都不满足需求,可以用自定义颜色作为种子:

dart
// 方式 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,
)

常用配置

dart
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

dart
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,你可以在浏览器中实时调整主题参数,然后把生成的代码复制到项目中:

👉 Themes Playground 在线版

使用流程:

  1. 在 Playground 中选择一个预设方案或自定义颜色
  2. 调整圆角、表面混合、AppBar 样式等参数
  3. 在代码面板中复制生成的 FlexThemeData 代码
  4. 粘贴到你的 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)

基于 Flutter 官方文档整理