跳转至

MaterialColorScheme - Material Design 3 颜色方案

MaterialColorScheme 是我们对 Material Design 3 颜色系统的完整实现。Material You 的核心就是动态颜色系统——从一个种子颜色自动生成 26 个语义化颜色,确保整个 UI 在视觉上协调一致。我们把这个系统做成了嵌入式 Token 注册表的形式,每个颜色方案实例都独立管理自己的颜色值。

Material Design 3 颜色系统

Material Design 3 的颜色系统是基于"色调调色板"(Tonal Palette)设计的,而不是传统 HSL/HSV。每个颜色角色都有对应的"On"颜色用于显示在其上方的文本。系统把颜色分成几个组:

  • Primary:主色,用于关键组件如按钮、活跃状态
  • Secondary:辅助色,用于次要组件和强调
  • Tertiary:第三色,用于平衡和表达品牌独特性
  • Error:错误色,用于错误状态和破坏性操作
  • Surface:表面色,包括背景、表面及其变体
  • Utility:工具色,支持阴影、遮罩和反色状态

基本用法

通过工厂函数创建颜色方案是最简单的方式:

#include "material_factory.hpp"

// 创建默认浅色主题(Material 基准紫色)
auto lightScheme = cf::ui::core::material::light();

// 创建默认深色主题
auto darkScheme = cf::ui::core::material::dark();

// 查询颜色
QColor primary = lightScheme.queryExpectedColor("md.primary");
QColor onPrimary = lightScheme.queryExpectedColor("md.onPrimary");
QColor surface = lightScheme.queryExpectedColor("md.surface");

颜色名称采用 md. 前缀,后跟 Material 官方定义的 token 名称。这样设计是为了和其他主题系统(比如我们未来可能实现的 Fluent)做区分。

颜色组访问

虽然可以用字符串查询,但类型安全的方式是通过颜色组访问器:

using namespace cf::ui::core;

MaterialColorScheme scheme = material::light();

// 获取主色组——返回的是包含 Token 类型的结构体
PrimaryColors primary = scheme.primary();
// 这些 Token 类型可以配合我们的 Token Registry 使用

// 需要实际颜色值时还是通过查询
QColor primaryColor = scheme.queryExpectedColor("md.primary");
QColor containerColor = scheme.queryExpectedColor("md.primaryContainer");

颜色组结构体(PrimaryColorsSecondaryColors 等)主要是为了类型安全和文档目的,实际颜色值还是从 registry 中查询。

从种子颜色生成

Material You 的特色是"动态颜色"——用户选一个喜欢的颜色,系统自动生成完整的配色:

#include "base/color.h"

// 从任意颜色生成配色
CFColor seedColor("#6750A4");
auto scheme = cf::ui::core::material::fromKeyColor(seedColor);

// 生成深色版本
auto darkScheme = cf::ui::core::material::fromKeyColor(seedColor, true);

这个功能内部使用 HCT 色彩空间和 Material 的色调调色板算法。HCT(Hue-Chroma-Tone)是 Material 团队专门开发的色彩空间,比 HSL 更符合人眼对颜色的感知——这也是为什么 Material 的配色看起来特别和谐的原因。

从 JSON 导入

Material Theme Builder 是 Google 官方的配色在线工具,导出的 JSON 我们可以直接解析:

QByteArray json = R"({
  "schemes": {
    "light": {
      "primary": "#6750A4",
      "onPrimary": "#FFFFFF",
      "primaryContainer": "#EADDFF",
      ...
    }
  }
})";

// 从 JSON 创建(指定使用 light 方案)
auto result = cf::ui::core::material::fromJson(json, false);

// cf::expected 风格的错误处理
if (result) {
    auto scheme = std::move(*result);
    // 使用 scheme
} else {
    const auto& err = result.error();
    if (err.kind == MaterialSchemeError::Kind::InvalidJson) {
        qDebug() << "JSON 解析失败:" << err.message.c_str();
    }
}

也支持直接传入颜色对象的方式(没有 schemes 包装的扁平结构)。

导出到 JSON

可以把当前配色导出为 JSON 格式,方便保存或分享:

MaterialColorScheme scheme = material::light();
QByteArray json = material::toJson(scheme);

// 可以保存到文件或传输
QFile file("my_theme.json");
file.open(QIODevice::WriteOnly);
file.write(json);

Token 注册表

每个 MaterialColorScheme 内部都有一个 EmbeddedTokenRegistry,用来存储所有颜色 Token:

MaterialColorScheme scheme = material::light();

// 直接访问底层注册表
auto& registry = scheme.registry();

// 可以手动修改或添加 Token
registry.set("md.customColor", QColor("#FF5722"));

这个设计让系统既支持预定义的 Material 颜色,也允许扩展自定义颜色。

缓存机制

颜色查询有缓存层,避免重复解析字符串:

// 第一次查询会查找并缓存
QColor color1 = scheme.queryColor("md.primary");

// 后续查询从缓存返回
QColor color2 = scheme.queryColor("md.primary");

缓存在 MaterialColorScheme 对象生命周期内有效。如果需要修改某个颜色后立即生效,直接修改 registry 即可——查询接口每次都会从 registry 读取最新值。

完整颜色列表

Material Design 3 定义了 26 个颜色角色,对应的查询名称如下:

主色组:
  md.primary, md.onPrimary, md.primaryContainer, md.onPrimaryContainer

辅助色组:
  md.secondary, md.onSecondary, md.secondaryContainer, md.onSecondaryContainer

第三色组:
  md.tertiary, md.onTertiary, md.tertiaryContainer, md.onTertiaryContainer

错误色组:
  md.error, md.onError, md.errorContainer, md.onErrorContainer

表面色组:
  md.background, md.onBackground, md.surface, md.onSurface,
  md.surfaceVariant, md.onSurfaceVariant, md.outline, md.outlineVariant

工具色组:
  md.shadow, md.scrim, md.inverseSurface, md.inverseOnSurface, md.inversePrimary

相关文档