### 3. 颜色系统基础:动态颜色(ColorScheme)、Seed Color生成、Light/Dark主题色板定义 Material 3 的颜色系统以 **动态颜色(Dynamic Color)** 为核心,它不再依赖固定的色板,而是通过一个 **Seed Color(种子色)** 自动生成一套包含主色、辅色、中性色等完整且协调的 **ColorScheme**。这套方案可同时适配 Light(亮色)和 Dark(暗色)两种主题模式,并能在运行时根据用户壁纸或品牌色动态切换。本章将深入原理、实现流程和常见问题。 --- #### 3.1 动态颜色 vs. 传统主题色 | 特性 | 传统主题色方案 | Material 3 动态颜色 | |------|----------------|----------------------| | 色板来源 | 开发者手动指定固定颜色(如 `#6200EE`) | 算法从 **Seed Color** 自动推算整个调色板 | | 系统适配 | 仅支持 Light/Dark 两套静态色板 | 通过 `lightColorScheme()` / `darkColorScheme()` 自动生成双模式版本 | | 可访问性 | 需要开发者手动检查对比度 | 内置 WCAG AA 级对比度算法,确保文字/图标可读 | | 动态更新 | 需重启应用或重新设置主题 | 支持实时切换 Seed Color,ColorScheme 自动重新生成 | --- #### 3.2 算法原理:从 Seed Color 到 ColorScheme Material 3 的颜色生成引擎基于 **HCT(Hue-Chroma-Tone)色彩空间**。流程如下: 1. **输入**:一个 Seed Color(例如 `#6750A4`,Material 3 默认紫色)。 2. **主色主色调** (Primary):通过算法调整 Hue 固定,Chroma 取最大值,Tone 选取 40(Light)或 80(Dark)作为 Primary 主色调。 3. **辅色 (Secondary)**:在同一个 Hue 上降低 Chroma,得到柔和版本。 4. **第三色 (Tertiary)**:Hue 旋转约 60 度,Chroma 取中等值,提供视觉对位。 5. **中性色 (Neutral / Neutral Variant)**:Chroma 压低至接近 0,用于背景、表面、卡片等大面积元素。 6. **误差修正**:对生成的每个颜色做对比度检测(如 Primary 与 Surface 对比度 ≥ 4.5:1),若不满足则自动微调 Tone。 生成完成后,Light 与 Dark 模式使用 **同一套 Tonal Palette**(色调板),但在不同模式下选取不同的 Tone 值(例如 Light 用 Tone 40 做 Primary,Dark 用 Tone 80 做 Primary),确保了双模式视觉统一且对比度合规。 --- #### 3.3 实现流程与代码示例 **步骤 1**:定义 Seed Color。建议从品牌色或壁纸主色中提取一个高饱和度的颜色。例如在支付 UI 中,可将品牌蓝 `#1565C0` 作为种子。 **步骤 2**:使用 Material 3 的 API 生成 ColorScheme。 以 Jetpack Compose 为例:
// 导入 Material 3 核心库
import androidx.compose.material3.*
import androidx.compose.ui.graphics.Color

// 定义种子色
val seedColor = Color(0xFF1565C0)

// 生成 Light 和 Dark 两套 ColorScheme
val lightColorScheme = lightColorScheme(
    primary = Color(0xFF1565C0),
    // secondary, tertiary 等由系统从 seedColor 推断
    // 但也可以手动覆盖:
    secondary = Color(0xFF5C6BC0)
)

val darkColorScheme = darkColorScheme(
    primary = Color(0xFF82B1FF),
    secondary = Color(0xFF9FA8DA)
)
**步骤 3**:在主题中应用 ColorScheme,并包装进 `MaterialTheme`:
@Composable
fun PaymentAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colorScheme = if (darkTheme) darkColorScheme else lightColorScheme
    MaterialTheme(
        colorScheme = colorScheme,
        typography = PaymentTypography,
        content = content
    )
}
**步骤 4**(高级):利用 `dynamicColorScheme()` 根据系统壁纸自动生成种子色(仅 Android 12+)。但支付类 UI 通常建议使用固定种子以保持品牌一致性,避免视觉跳跃。 --- #### 3.4 调试与验证 - **可视化调试**:使用 Material 3 提供的 [Color Tool](https://m3.material.io/theme-builder) 或 Android Studio 的 Theme Preview 插件,输入种子色后预览 Light/Dark 两个面板的效果。 - **对比度检查**:在代码中打印 `ColorScheme.primary` 与 `ColorScheme.surface` 的亮度差,确保 ≥ 4.5:1:
// 伪代码示例(Kotlin)
val luminancePrimary = Color.luminance(lightColorScheme.primary)
val luminanceSurface = Color.luminance(lightColorScheme.surface)
val contrast = (max(luminancePrimary, luminanceSurface) + 0.05) /
               (min(luminancePrimary, luminanceSurface) + 0.05)
Log.d("Contrast", "Primary vs Surface: $contrast")
- **常见错误**: - ❌ 种子色亮度太低(如 `#000000`),导致亮色主题下 Primary 过暗、图标对比度不足。 - ❌ 在 Dark 模式下使用和 Light 相同的 Tone 值(如 Primary Tone 40 在深色背景上几乎不可见)。 - ❌ 手动覆盖 `secondary`、`tertiary` 但未与种子色算法保持 Hue 一致性,导致颜色冲突。 --- #### 3.5 性能与稳定性优化 - **缓存 ColorScheme**:在 `@Composable` 外部将 lightColorScheme / darkColorScheme 声明为 `val` 或 `remember`,避免每次重组时重新生成(生成算法少量计算,但在列表频繁刷新时可能产生微卡顿)。 - **预计算 Tonal Palette**:若应用需要频繁动态切换种子色(例如用户自定义主题),可将 Tonal Palette 计算放在 `LaunchedEffect` 或协程中,仅在种子变化时更新。 - **避免频繁动态颜色**:支付 UI 中通常固定品牌色,无需高频率动态变化,因此直接使用 `lightColorScheme` / `darkColorScheme` 即可,无需启用系统壁纸动态颜色。 --- #### 3.6 流程图:Seed Color → ColorScheme 生成过程 Seed Color → ColorScheme 生成流程 Seed Color HCT 算法生成 Tonal Palette (Primary / Secondary / Tertiary / Neutral) Light 色调选择 Dark 色调选择 lightColorScheme (Primary: Tone 40, …) darkColorScheme (Primary: Tone 80, …) Light/Dark 两个 ColorScheme 共用同一 Tonal Palette,仅 Tone 值不同 ---
💡 实践建议:在支付 UI 中,种子色应选择品牌蓝色或品牌紫色。不要选择纯黑/纯白或过低饱和度的颜色,否则算法生成的辅色、中性色可能丧失层次感。使用 lightColorScheme() 和 darkColorScheme() 时,若对特定颜色(如 error)有特殊需求,可直接覆盖传入参数,算法会保留其他自动生成的值。
--- #### 3.7 常见错误与避坑指南 | 错误现象 | 原因 | 解决方案 | |----------|------|----------| | 亮色主题下按钮文字和背景对比度不足 | Primary 和 Surface 的 Tone 值过于接近 | 检查种子色亮度,选择亮度在 20%–80% 之间的颜色;或手动提高 Primary 与 Surface 的亮度差 | | 暗色主题下 Primary 几乎不可见 | 使用了 Light 模式下的 Tone 40 作为 Dark 模式 Primary | 确保使用 `darkColorScheme` 自动生成的 Tone 80+ 版本;不要复用 Light 的色值 | | 第三方输入框背景色与系统状态栏混淆 | Neutral 和 NeutralVariant 色调差异小 | 在 `lightColorScheme` 中明确指定 `surfaceVariant` 和 `background` 的色值,或使用 `dynamicColorScheme` 并指定对比度保底参数 | | 调整 Seed Color 后 UI 没有实时更新 | ColorScheme 未使用 `remember` 或 `derivedStateOf` | 将种子色定义为 `mutableStateOf` 并包装在 `MaterialTheme` 外部,通过 `derivedStateOf` 生成新的 ColorScheme | ---
📘 核心概念总结
· 动态颜色的本质是 Seed Color → HCT 算法 → Tonal Palette → Light/Dark ColorScheme。
· 同一 Tonal Palette 在 Light 模式下取低 Tone,Dark 模式下取高 Tone,保证可读性。
· 支付 UI 建议固定种子色,避免动态壁纸切换带来的品牌色混乱;但可以利用 lightColorScheme 和 darkColorScheme 的自动生成节省手动配色调优时间。
· 调试时务必用对比度工具验证 Primary 与 Surface、OnPrimary 与 Primary 等关键组合。
--- 至此,你已经掌握了 Material 3 颜色系统的核心构建策略。下一节将深入排版与间距系统的标准化配置,确保支付 UI 在各尺寸屏幕上保持统一视觉节律。