3、自定义主题颜色:深入 ColorScheme,创建品牌专属调色板,覆盖默认颜色

说实话,Flutter 默认的 Material Design 颜色挺好看的。但你要是做商业项目,客户第一句话往往是:「能不能换成我们公司的品牌色?」

嗯,这时候就得跟 ColorScheme 打交道了。我刚开始接触时也觉得这东西有点绕,但摸清楚规律后,你会发现它其实就是一个「颜色管理系统」——你给它一套品牌色,它帮你搞定全局的明暗适配、状态变化。

3.1 ColorScheme 到底是什么?

说白了,ColorScheme 就是 Flutter 里定义的一套「语义化颜色」。它不像以前那样让你手动去配每个按钮、每个背景的颜色,而是定义好「主色」、「次要色」、「错误色」等角色,框架自动帮你应用到各个组件上。

举个例子:

// 默认的 ColorScheme
ColorScheme.defaultScheme
  .copyWith(primary: Colors.blue)

这样一改,所有用到 primary 的地方——FloatingActionButton、AppBar、选中状态——全都会变成蓝色。省心吧?

我的习惯: 我一般不会直接改默认的 ColorScheme,而是从品牌色出发,用 ColorScheme.fromSeedColorScheme.fromSwatch 生成一套完整的调色板。这样明暗色系会自动匹配,省去手动配对的麻烦。

3.2 从品牌色生成调色板

假设你们公司的品牌色是 #FF6B35(一种活力橙)。你当然可以手动配一套深浅色,但更聪明的做法是让 Flutter 帮你算。

final brandColor = const Color(0xFFFF6B35);

final lightScheme = ColorScheme.fromSeed(
  seedColor: brandColor,
  brightness: Brightness.light,
);

final darkScheme = ColorScheme.fromSeed(
  seedColor: brandColor,
  brightness: Brightness.dark,
);

你看,只需要一个种子颜色,Flutter 就会自动生成 primarysecondarysurfaceonPrimary 等十几个颜色。而且明暗两套都给你算好了。

我在项目中遇到过一个问题:客户给的品牌色只有一种,但我们需要支持深色模式。手动配深色版太痛苦了,用 fromSeed 几行代码就搞定。客户看了深色模式的效果,直呼「你们怎么知道我想要的暗橙色是这样的?」

3.3 手动覆盖特定颜色

自动生成虽然方便,但有时候品牌规范里明确规定了某些颜色。比如品牌手册里写着「主色 #FF6B35,次要色 #2D3142,背景色 #F8F9FA」。

这时候就需要手动覆盖了:

final customScheme = ColorScheme(
  brightness: Brightness.light,
  primary: const Color(0xFFFF6B35),
  onPrimary: Colors.white,
  secondary: const Color(0xFF2D3142),
  onSecondary: Colors.white,
  surface: const Color(0xFFF8F9FA),
  onSurface: const Color(0xFF1A1A2E),
  error: const Color(0xFFD32F2F),
  onError: Colors.white,
);
我曾经踩过的坑: 手动创建 ColorScheme 时,漏掉了 onPrimaryonSurface 这些「反色」。结果按钮上的文字变成了白色底+白色字,完全看不见。记住:每个颜色都要配一个对应的「前景色」,否则 UI 会出大问题。

3.4 覆盖默认颜色的最佳实践

你可能会问:是不是所有颜色都要手动配?当然不是。我建议采用「分层覆盖」的策略:

层级 方法 适用场景
第一层 ColorScheme.fromSeed 只有品牌主色,其他让框架自动生成
第二层 .copyWith 自动生成后,微调个别颜色
第三层 手动创建 ColorScheme 品牌规范严格,每个颜色都有明确值

我个人习惯先用 fromSeed 生成,再用 copyWith 覆盖不符合品牌规范的地方。这样既省事,又不会偏离品牌太远。

final baseScheme = ColorScheme.fromSeed(
  seedColor: brandColor,
  brightness: Brightness.light,
);

final finalScheme = baseScheme.copyWith(
  secondary: const Color(0xFF2D3142),
  surface: const Color(0xFFF8F9FA),
);

3.5 应用到 ThemeData

调色板配好了,怎么用?很简单,塞进 ThemeData 里:

final theme = ThemeData(
  useMaterial3: true,
  colorScheme: finalScheme,
);

然后整个 App 的 Material 组件都会自动适配这套颜色。你想想看,以前改个主题色要改多少个地方?AppBar、Button、TextField、Switch……现在一个 colorScheme 全搞定。

关键点: 记得开启 useMaterial3: true。Material 2 虽然也支持 ColorScheme,但 Material 3 对颜色的语义化更彻底,组件对 colorScheme 的响应也更完整。

3.6 实战:品牌色 + 深色模式

最后给你一个完整的实战模板。我在好几个项目里都用这个结构:

class AppTheme {
  static const Color _brandColor = Color(0xFFFF6B35);

  static ThemeData light() {
    final scheme = ColorScheme.fromSeed(
      seedColor: _brandColor,
      brightness: Brightness.light,
    ).copyWith(
      secondary: const Color(0xFF2D3142),
    );

    return ThemeData(
      useMaterial3: true,
      colorScheme: scheme,
    );
  }

  static ThemeData dark() {
    final scheme = ColorScheme.fromSeed(
      seedColor: _brandColor,
      brightness: Brightness.dark,
    ).copyWith(
      secondary: const Color(0xFFB0BEC5),
    );

    return ThemeData(
      useMaterial3: true,
      colorScheme: scheme,
    );
  }
}

使用时:

MaterialApp(
  theme: AppTheme.light(),
  darkTheme: AppTheme.dark(),
  themeMode: ThemeMode.system,
)

嗯,这样一套品牌专属的调色板就搞定了。用户切深色模式时,颜色会自动切换,而且所有组件都保持一致风格。

避坑指南: 我曾经在深色模式下直接用 light().copyWith(brightness: Brightness.dark),结果颜色对比度完全不对。记住:明暗两套调色板要分别用 fromSeed 生成,不要偷懒只改一个 brightness 属性。

总结一下:ColorScheme 是 Flutter 主题定制的核心。从品牌色出发,用 fromSeed 自动生成,再用 copyWith 微调,最后塞进 ThemeData。这套流程我用了两年,稳得很。