Material Design 3 的更新,视觉规范又变了
「Material Design 3 的更新,视觉规范又变了」
Google I/O 2024 之后,Material Design 3 又推了一波更新。我盯着那篇官方博客看了三遍,确认自己没穿越回 2021 年。当年 Material You 刚出来,Dynamic Color 被吹成 Android 12 的标志性特性,现在三年过去,规范文档里悄悄多了几个 "deprecated" 标签,一些之前力推的组件用法被推翻,新的 token 命名体系又双叒叕变了。这不是正常的迭代演进,这是 Google 设计团队在公开演示什么叫 "自己打自己脸"。
从 Material You 到 Material Design 3:名字游戏玩够了没
先理清楚这堆名字的混乱史。2014 年 Material Design 诞生,扁平化、阴影层级、物理隐喻,这套设计语言让 Android 第一次有了统一的美学体系。2018 年 Material Design 2 推出,核心变化是更灵活的组件系统,加了 Material Theming,让开发者能自定义品牌色、圆角、字体。然后 2021 年 Android 12 发布,Google 突然宣布 Material You,强调个性化、动态取色、以用户为中心的界面。再后来官方文档里 Material You 慢慢被 Material Design 3 取代,现在搜 Material You 的文档链接很多都 301 跳转到 Material Design 3 页面了。
名字改来改去不只是营销话术的问题。它反映了 Google 设计团队内部的方向摇摆。Material You 时期的核心理念是 "your phone, your color",系统从壁纸提取色板,应用到整个系统 UI 和适配的第三方应用。当时这个理念被吹得很高,甚至成了 Pixel 手机的差异化卖点。但三年实践下来,这套系统的工程落地问题暴露得很明显。
动态取色的算法在 Android 12 初期版本 bug 一堆。我印象很深的是 2021 年底,Pixel 6 刚上市,用户设置某些深色壁纸后,系统提取的主色调会偏到紫色或棕色,和壁纸实际色调完全不搭。Google 在 Android 12L 和 13 里修了几轮,但根本问题是这套色彩提取逻辑(com.google.android.material:material 库里的 DynamicColors 类)对壁纸的亮度分布极其敏感,暗部细节多的照片容易抽风。到了 Android 14,Google 默默在文档里加了句 "动态颜色是可选功能,应用应提供合理的默认色板",这等于变相承认不是所有场景都适合强推 Dynamic Color。
更讽刺的是 Material You 最标志性的 "大色块" 风格。Android 12 的设置界面、快捷设置面板用了大面积的 pill 形状按钮和高饱和色块,当时被批评像 "幼儿园积木"。Google 设计师在 Medium 上发了好几篇文章解释这种 "expressive" 风格的合理性。结果 Material Design 3 的 2024 更新里,规范悄悄往回拉了:按钮默认高度从 56dp 降到 48dp,大面积色块的使用被建议 "仅在关键行动点使用",设置界面的密度要求提高,留白增加。这基本就是承认之前的大色块实验失败了,但官方不会这么说,只会用 "refined for better usability" 这种话术包装。
Token 体系的第三次重构:开发者的时间不是时间
如果你维护过使用 Material Design Components (MDC) 的 Android 项目,一定对 theme attribute 的混乱深有体会。Material Design 2 时期用 ?attr/colorPrimary、?attr/colorOnPrimary 这套命名,已经比 Android 原生 ?android:attr/colorPrimary 要复杂一层。Material You 引入 Dynamic Color 后,token 体系膨胀到离谱的程度。
Android 的 material-components 库在 1.5.0 版本(对应 Material You)引入了 ColorScheme 的完整 token 列表:primary、onPrimary、primaryContainer、onPrimaryContainer、secondary、onSecondary... 光是 "primary" 相关的 surface 色就有四种变体。然后 1.7.0 版本加了 "surface" 系列的扩展:surface、onSurface、surfaceVariant、onSurfaceVariant、surfaceTint、inverseSurface、inverseOnSurface... 到了 1.11.0 和 1.12.0,Google 又推新的 token 命名:surfaceContainerHighest、surfaceContainerHigh、surfaceContainer、surfaceContainerLow、surfaceContainerLowest,用来替代之前的 surfaceVariant。
这意味着什么?假设你的项目从 Material Design 2 升级到 Material Design 3,你需要把 ?attr/colorSurfaceVariant 改成 ?attr/colorSurfaceContainerHighest 或 colorSurfaceContainerHigh,具体选哪个取决于你想要的视觉层级。但官方文档对 "High" 和 "Highest" 的区别描述模糊得很,基本就是 "use your best judgment"。更坑的是,这些 token 在 Compose Material3 库和 View 系统的 MDC 库之间不完全一致。Compose 的 MaterialTheme.colorScheme.surfaceContainerHigh 和 View 的 ?attr/colorSurfaceContainerHigh 在相同主题下可能渲染出不同的色值,因为两个库的版本迭代不同步。
我在一个维护了四年的企业级项目里做 M3 迁移时,光是梳理 token 映射就花了两天。设计团队给的 Figma 文件用的是最新规范,但我们的 MDC 库版本锁在 1.9.0 因为某些兼容性问题,结果有些新 token 根本不存在,只能硬编码颜色值或者自定义 attr。这种体验极其糟糕,尤其当你知道再过半年 Google 可能又改命名的时候。
Compose 的情况更乱。androidx.compose.material3:material3 库从 1.0.0 到 1.2.0 再到现在的 1.3.0-beta,ColorScheme 的构造函数参数变了三次。1.0.0 有 surfaceVariant,1.2.0 标记为 deprecated 推荐用 surfaceContainerHighest,但 1.2.0 里 surfaceContainerHighest 的类型是 Color 而 surfaceVariant 也是 Color,没有编译期强制,只有 IDE 的 strikethrough 提示。很多开发者懒得改,直到某天升级版本发现 surfaceVariant 被彻底移除,项目编译报错。
Google 的迁移策略从来都是 "breaking changes first, migration guides later"。Material Design 3 的 token 重构有官方迁移文档,但文档本身都在迭代,去年写的迁移指南今年就过时了。社区里有个很经典的吐槽:MDC 的 migration guide 需要它自己的 migration guide。
组件规范的反复:Bottom Sheet 的典型案例
看一个具体组件的演变,能更清楚感受到这种混乱。Bottom Sheet 在 Material Design 2 时期有明确的规范:modal bottom sheet 用于展示阻断性内容,standard bottom sheet 用于持续存在的辅助信息,高度、阴影、圆角都有固定值。
Material You 时期,Google 突然力推 "Large Bottom Sheet",也就是几乎占满全屏的底部弹层,圆角拉到 28dp,顶部有个 drag handle。这个设计在 Google 自家的 Google Maps、Google Photos 里大量应用,当时被当成 Material You 的标志性交互模式。Android 的 material-components 库在 1.7.0 给 BottomSheetDialogFragment 加了 enableEdgeToEdge 和新的默认高度计算逻辑,让 sheet 更容易扩展到全屏。
但问题很快暴露。Large Bottom Sheet 在物理返回键的处理上和系统导航手势冲突严重。Android 的预测性返回手势(Predictive Back Gesture)在 Android 13 引入、14 强化,用户从屏幕边缘侧滑返回时,系统期望看到上一个界面的预览。但一个占满 90% 屏幕的 Bottom Sheet 侧滑时,到底是关闭 sheet 还是返回上一个 Activity?Google 自家的应用在这个问题上都处理得不一致。Google Photos 里侧滑是关闭 sheet,Google Maps 里有时候是返回有时候是关闭,取决于 sheet 的展开状态。
Material Design 3 的 2024 更新里,Bottom Sheet 的规范又调整了。官方现在建议 "modal bottom sheet 不应超过屏幕高度的 50%,除非内容需要",drag handle 从必须变成可选,圆角默认值从 28dp 降到 16dp。这和三年前的方向基本是 180 度转弯。更微妙的是,material-components 库 1.12.0 里 BottomSheetBehavior 的默认 peekHeight 计算逻辑改了,旧项目升级后可能出现 sheet 展开高度不对的问题,issue tracker 上 #3527、#3601 都是这类回归 bug。
Compose 的 Bottom Sheet 实现更是一场灾难。ModalBottomSheet 在 Compose Material3 1.0.0 是 experimental API,1.1.0 转正但参数列表大变,1.2.0 又加了 sheetMaxWidth、contentWindowInsets 等新参数。最坑的是 ModalBottomSheet 的显示状态管理:1.0.0 用 ModalBottomSheetState,1.1.0 推荐改用 SheetState,但两个类的构造函数参数和行为不完全等价。我在一个项目里从 1.0.0 升级到 1.2.0,Bottom Sheet 的动画曲线变了,因为 SheetState 的默认动画 spec 和原来的 ModalBottomSheetState 不同,官方文档完全没提这个 breaking change,是在 Stack Overflow 上翻到一个 Google 工程师的回复才确认。
跨平台野心的代价:Compose Multiplatform 的 Material3 困境
Material Design 3 的混乱不只是 Android 的问题。JetBrains 推 Compose Multiplatform 之后,Material3 被当成跨平台 UI 的默认设计系统,支持 Android、iOS、Desktop(JVM)、Web(WASM/JS)。但 Google 的 Material3 库只官方维护 Android 版本,其他平台的实现由 JetBrains 和社区维护。
这导致了一个很尴尬的局面:Compose Multiplatform 的 material3 artifact 版本号和 Android 的不完全对应。Android 端 androidx.compose.material3:material3:1.2.1 对应的跨平台版本可能是 org.jetbrains.compose.material3:material3-desktop:1.6.0,但两个版本包含的组件和 API 可能有差异。比如 NavigationBar(就是以前的 BottomNavigation)在 Android Material3 1.2.0 支持了新的 indicator animation,但桌面版的同样版本可能没同步这个改动。
更深层的问题是设计规范本身对跨平台场景的不适应。Material Design 3 大量依赖 Android 的系统特性:Dynamic Color 需要 Android 的 WallpaperManager 和 Monet 引擎,状态栏颜色适配需要 WindowInsetsController,导航手势处理依赖 Android 的预测性返回 API。这些在 iOS 和 Desktop 上要么没有直接对应,要么行为完全不同。
JetBrains 的解决方案是提供 "expect/actual" 机制让开发者自己平台适配,但这等于把设计系统的完整性责任推给了应用开发者。一个用 Compose Multiplatform 的团队告诉我,他们为了在多平台保持视觉一致,最后放弃了 Material3 的动态颜色特性,全部硬编码色值,自己维护一套 token 系统。这基本就是绕开 Material Design 3 的核心价值,把它当成一个普通组件库在用。
Google 对此的态度很暧昧。官方 Compose Multiplatform 的文档里会提到 Material3,但从不承诺跨平台 API 的同步更新。2024 年的 Material Design 3 更新博客只字未提 Compose Multiplatform,全是 Android、Wear OS、TV 和 XR(Google 新推的扩展现实平台)的内容。这种选择性忽视让依赖跨平台的开发者很没安全感——你不知道今天用的 API 明天在另一个平台还能不能编译。
设计 token 的工程化悖论:Figma 和代码的鸿沟
Google 推 Material Design 3 时一个卖点是更好的设计-开发协作流程。Material Theme Builder、Figma 插件、官方 token 仓库(material-design-tokens),意图是让设计师在 Figma 里选的 token 能直接映射到代码里的 theme attribute。
理想很丰满。实际项目中,Figma 插件导出的 token 和代码库用的版本经常对不上。Material Theme Builder 网站(material-foundation.github.io/material-theme-builder)到现在还是基于某个中间版本的规范,导出的 XML/JSON 里可能包含已经被重命名的 token。我 2023 年用这个工具导出一套色板,发现里面有 surface1、surface2、surface3、surface4、surface5,但 MDC 库 1.9.0 里根本没有这些 attr,对应的是 elevationOverlayEnabled 配合 elevation 的混合逻辑。后来才搞清楚,surface1-5 是 Material You 早期实验性的 token,在正式版 Material Design 3 里被废弃了,但 Theme Builder 没更新。
Figma 的 Material 3 Design Kit 也是版本混乱的重灾区。官方 Figma 社区文件有多个版本,有些标着 "v1.12" 有些标着 "M3 Stable",但组件结构不同。设计团队如果用了旧版 kit,和开发对接时会出现 "这个组件在代码库里不存在" 或者 "这个 token 名代码里没有" 的情况。更麻烦的是 Figma 的 variant 系统和代码的 Compose/View 实现逻辑不完全一致,比如 Figma 里一个 Button 组件通过 variant 切换 contained/outlined/text/elevated/tinted 五种类型,但代码里这对应五个不同的 Composable(Button、OutlinedButton、TextButton、ElevatedButton、FilledTonalButton),不是同一个组件的参数切换。
Google 2024 年推了一个新的设计 token 格式叫 "Material Tokens (MTokens)",试图用更结构化的 JSON Schema 解决这个问题。但 MTokens 的 spec 目前还是 alpha 状态,和实际代码库的映射关系不明确。我看到这个新闻的第一反应是:你们先把现有三个版本的 token 统一了再推新格式不行吗?
与其他设计系统的对比:不是拉踩,是事实
我不想说 "苹果怎么做" 或者 "Flutter 怎么做" 来衬托 Material Design 3 的问题,但有些对比确实能说明问题。
Apple 的 Human Interface Guidelines 在视觉风格上变化也很大,从 iOS 6 的拟物到 iOS 7 的扁平到 iOS 18 的 "liquid glass",但每次风格转型的工程落地相对干净。SwiftUI 的 API 虽然也有演进,但 Color.primary、Color.secondary 这种基础 token 的语义保持稳定,不会今年叫 surfaceVariant 明年叫 surfaceContainerHighest。Apple 的设计 token 体系不追求过度细分,iOS 的系统色板就十几种基础颜色,开发者自己扩展。
Flutter 的 Material Design 实现(flutter/material.dart)是另一个参照。Flutter 团队维护的 Material 库跟进 Google 的规范更新,但因为 Flutter 的跨平台定位和版本发布节奏,它往往会跳过一些 "实验性" 的规范波动。比如 Flutter 3.x 的 Material 3 实现里,ColorScheme 的字段命名相对保守,没有盲目跟进 Android MDC 库每一次 token 重命名。Flutter 的 surfaceVariant 至今还在用,没有被 deprecated,因为 Flutter 团队评估了 breaking change 的成本和收益,选择了更稳定的策略。
Google 内部的不同团队对 Material Design 3 的跟进程度也不同。Chrome OS 的 UI 在 Material You 时期做了大改,但 2024 年很多界面又往回退,更接近 Material Design 2 的密度。Wear OS 的 Material 3 规范是单独的一套,圆角、间距和手机的规范不同。Android TV 的 Material 3 又有自己的 focus 状态和远距离可视性调整。同一个 "Material Design 3" 品牌下,实际落地的是多套碎片化的规范,开发者如果做全平台适配,要记的规则成倍增加。
一个具体项目的迁移血泪史
说个我亲历的案例,可能比抽象批评更有说服力。2022 年,我参与的一个电商 App 决定从 Material Design 2 升级到 Material You,目标是适配 Android 12+ 的动态颜色和新的视觉风格。当时评估工作量是两周,实际花了两个月。
第一个坑是主题继承链。MDC 1.5.0 的 Theme.Material3.DayNight 和原来的 Theme.MaterialComponents.DayNight 不是简单的替换关系。Material3 主题默认启用了 android:enforceNavigationBarContrast 和 android:statusBarColor 的新行为,导致状态栏在浅色主题下变成半透明的灰色而不是纯白,设计团队不认可这个效果,需要手动覆盖。
第二个坑是组件行为的隐性变化。MaterialButton 在 Material3 默认样式从 ?attr/materialButtonStyle 变成了 ?attr/materialButtonOutlinedStyle 的某种混合,具体逻辑我到现在没完全搞清楚,结果是同样一个 layout 文件,把 theme parent 改成 Material3 后,按钮的默认高度、padding、文字大小全变了。这不是 bug,是 "by design",但设计文档里没明确列出每个组件的默认样式变化清单。
第三个坑是 Dynamic Color 的覆盖率。我们按官方指南在 Application 里调用了 DynamicColors.applyToActivitiesIfAvailable(this),但测试发现 Android 12 的某些 OEM 设备(主要是三星和小米的部分机型)上,系统返回的 color palette 和 Pixel 设备差异很大,有些甚至返回全灰的色板。后来查 issue tracker,发现是这些 OEM 修改了 Monet 引擎的实现,或者禁用了第三方应用读取系统壁纸色的权限。Google 的官方文档对此只有一句 "Dynamic Color availability is device dependent",没有提供可靠的运行时检测方法。我们最后加了设备白名单,只在 Pixel 和少数确认兼容的机型上启用 Dynamic Color,其他 fallback 到静态色板——这基本否定了 Material You 的核心价值主张。
2024 年,当我们准备跟进 Material Design 3 的新更新时,发现又要面对一轮 token 重命名和组件行为调整。这次团队直接否决了全面迁移的方案,决定冻结在 MDC 1.9.0 和 Compose Material3 1.1.0,只 cherry-pick 必要的 bug fix。这不是技术保守,是成本收益权衡后的理性选择。
Google 的设计决策机制:为什么总是这样
Material Design 3 的反复变动,根源在于 Google 的设计决策机制和工程落地的脱节。
Google 的设计团队(Material Design 团队属于 Google Design)有很强的话语权,但他们的工作节奏和 Android 平台的实际迭代不完全同步。Material You 的核心理念——动态颜色、个性化表达——更像是设计团队先有了愿景,再倒逼工程团队实现。Android 12 的 Monet 引擎就是在这种压力下赶工出来的,初期版本的质量问题有目共睹。
另一方面,Google 的产品线太广,设计规范要服务 Android、Wear、TV、Auto、XR、Chrome OS、Web(通过 Material Web),每个平台有自己的约束和优先级。Material Design 3 的更新往往以某个平台的需求为主导,然后试图泛化到其他平台,结果就是不合适的抽象和频繁的回退。
还有一个因素是 Pixel 手机的硬件策略。Pixel 需要差异化卖点,Material You 的视觉风格很大程度上是为 Pixel 的硬件营销服务的。当 Pixel 的销量和市场影响力没达到预期,或者营销重点转向其他功能(比如现在的 AI),设计规范的推广力度就会变化。Material Design 3 的 2024 更新里大量提到 "AI-powered UI" 和 "generative design",明显是在追 Gemini 的热点,但具体怎么落地很模糊,有种 "先占坑再说" 的感觉。
对比之下,Apple 的设计规范服务于统一的硬件生态和明确的商业目标,决策链条更短。Flutter 的设计实现由单一团队维护,可以为了稳定性牺牲规范的即时跟进。Google 的 Material Design 卡在中间:既要当 Android 的平台规范,又要服务跨平台野心,还要配合硬件营销,结果哪个都没做好。
开发者该怎么办:一些不完美的建议
面对这种局面,我个人有几个实践原则,不一定对,但确实减少了麻烦。
第一,延迟跟进最新版本。MDC 和 Compose Material3 的新版本至少等两个 patch release 再升级,让社区先踩坑。1.12.0 刚出的时候 issue tracker 上一堆 regression,1.12.1 和 1.12.2 修了不少。Google 的 release note 经常漏写 breaking change,看 GitHub 的 commit history 比看官方博客靠谱。
第二,隔离设计 token。不要把 ?attr/colorSurfaceContainerHigh 这种具体 token 直接写在 UI 代码里,包一层自己的 semantic token,比如 ?attr/colorCardBackground、?attr/colorDivider。这样 Google 改名的时候,只需要在 theme 文件里改一次映射,不用全项目搜索替换。这个做法在大型项目里是基本操作,但 Material Design 3 的频繁变动让它从 "最佳实践" 变成了 "生存必需"。
第三,谨慎使用实验性 API。Compose 里标 @ExperimentalMaterial3Api 的东西,生产环境尽量别用。不是因为它功能不稳定,而是因为它的 API 形状大概率会变,升级成本不可控。ModalBottomSheet 从 experimental 到 stable 的过程就是典型案例。
第四,对 Dynamic Color 保持理性。如果你的应用品牌色很强(比如电商、金融),Dynamic Color 可能反而会稀释品牌识别。Google 自家的应用里,Gmail、Google Drive 对 Dynamic Color 的采纳就很克制,只有设置界面和少量装饰元素跟随系统,核心功能界面保持品牌蓝。这不是技术限制,是产品决策。Material You 的理想是 "your phone, your color",但商业应用的现实是 "our app, our brand"。
规范的意义是什么
写到这里,我想回到一个根本问题:设计规范的价值到底是什么?
好的设计规范应该降低决策成本,让开发者和设计师把精力放在产品本身,而不是纠结 "这个按钮圆角应该是 8dp 还是 12dp"。Material Design 早期的成功在于此——它提供了一套完整、一致、有文档支撑的设计语言,Android 应用第一次有了 "看起来是同一平台" 的质感。
但 Material Design 3 的现状是反过来的。Token 的过度细分、命名的频繁变动、组件行为的不稳定,反而增加了决策负担。开发者需要花大量时间跟踪规范更新、处理版本迁移、协调设计和代码的差异。规范从工具变成了负担。
Google 似乎没意识到这个问题,或者意识到了但无力解决。2024 年的 Material Design 3 更新博客依然充满 "more expressive"、"more flexible"、"more personal" 这种空洞形容词,对具体的迁移成本、版本兼容性避而不谈。这种沟通方式让开发者感觉自己的实际困难被忽视了。
我有时候怀疑,Material Design 团队是不是把 "更新频率" 当成了 "活跃度" 的指标。每年必须有新东西发布,即使只是重命名几个 token 或者调整组件默认值。这种 KPI 驱动的迭代对生态是有害的,尤其当 breaking changes 被包装成 "refinements" 的时候。
一个设计系统成熟的表现,应该是规范的稳定性、API 的向后兼容承诺、清晰的迁移路径。Material Design 3 在这三点上都做得不够好。它还在青春期,但已经背负了太多平台的期望和商业的压力。
下次 Google I/O,如果 Material Design 团队宣布 "Material Design 4" 或者又一轮 token 重命名,我不会惊讶。只是希望那时候,他们能先回答一个问题:这次改动,到底解决了什么之前没解决的问题?还是只是为了证明这个团队还在工作?
我现在看到 Figma 里设计师新放的 Material Design 3 组件,第一反应不是 "这个设计好不好",而是 "这个组件在代码库的哪个版本里存在,命名有没有改,行为是不是又变了"。这种条件反射式的警惕,本身就是一个设计系统失败的信号。