鸿蒙Flutter实战:Material 3种子色亮暗双主题系统
前言Flutter 3.x 开始Material 3M3成为默认设计语言。相比 Material 2M3 最大的变化之一是动态配色——通过一个种子色自动生成整个应用的色调系统包括主色、次要色、表面色、错误色以及它们在不同亮度等级下的变体。鸿蒙 Flutter 备忘录使用薄荷绿#4DB6AC作为种子色同时支持亮色和暗色双主题跟随系统设置自动切换。本文拆解这套主题系统的设计和实现。项目仓库todo_flutter_harmony为什么是 ColorScheme.fromSeedMaterial 2 时代开发者需要手动定义 8-12 个颜色值来构建一个完整的主题// Material 2 —— 繁琐ThemeData(primaryColor:Color(0xFF4DB6AC),primaryColorLight:Color(0xFF80CBC4),primaryColorDark:Color(0xFF00897B),accentColor:Color(0xFFFF8A65),// ... 还要 backgroundColor, surfaceColor, errorColor 等等)Material 3 的ColorScheme.fromSeed让你只需要一个颜色// Material 3 —— 简洁ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:constColor(0xFF4DB6AC),brightness:Brightness.light,),)fromSeed内部使用基于 HCTHue-Chroma-Tone色彩空间的算法自动生成 30 个色调变体。这个算法由 Google 的 Material Design 团队开发考虑了人眼对不同色调的敏感度差异生成的色板在任何组合下都能保持足够的对比度。App 入口ThemeMode 和 ThemeDataclassAppextendsStatefulWidget{overrideStateAppcreateState()_AppState();}class_AppStateextendsStateApp{overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:芯捷备忘录,debugShowCheckedModeBanner:false,themeMode:ThemeMode.system,// 跟随系统设置theme:_buildLightTheme(),darkTheme:_buildDarkTheme(),home:constHomePage(),onGenerateRoute:_generateRoute,);}themeMode: ThemeMode.system应用自动跟随系统的亮/暗模式设置theme系统为亮色模式时的主题darkTheme系统为暗色模式时的主题如果想让用户手动控制而不是跟随系统可以换成themeMode:ThemeMode.light,// 始终亮色themeMode:ThemeMode.dark,// 始终暗色亮色主题ThemeData_buildLightTheme(){finalcolorSchemeColorScheme.fromSeed(seedColor:constColor(0xFF4DB6AC),// 薄荷绿brightness:Brightness.light,);returnThemeData(useMaterial3:true,colorScheme:colorScheme,appBarTheme:AppBarTheme(centerTitle:true,backgroundColor:colorScheme.surface,foregroundColor:colorScheme.onSurface,elevation:0,scrolledUnderElevation:1,),cardTheme:CardTheme(elevation:1.0,shadowColor:colorScheme.shadow.withOpacity(0.3),shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(16),),clipBehavior:Clip.antiAlias,),navigationBarTheme:NavigationBarThemeData(elevation:2,indicatorColor:colorScheme.primaryContainer,surfaceTintColor:colorScheme.surfaceTint,),floatingActionButtonTheme:FloatingActionButtonThemeData(backgroundColor:colorScheme.primaryContainer,foregroundColor:colorScheme.onPrimaryContainer,elevation:4,),inputDecorationTheme:InputDecorationTheme(border:OutlineInputBorder(borderRadius:BorderRadius.circular(12),),contentPadding:constEdgeInsets.symmetric(horizontal:16,vertical:14),),snackBarTheme:SnackBarThemeData(behavior:SnackBarBehavior.floating,shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(10)),),);}暗色主题ThemeData_buildDarkTheme(){finalcolorSchemeColorScheme.fromSeed(seedColor:constColor(0xFF4DB6AC),brightness:Brightness.dark,);returnThemeData(useMaterial3:true,colorScheme:colorScheme,appBarTheme:AppBarTheme(centerTitle:true,backgroundColor:colorScheme.surface,foregroundColor:colorScheme.onSurface,elevation:0,scrolledUnderElevation:1,),cardTheme:CardTheme(elevation:1.0,shadowColor:Colors.black.withOpacity(0.3),shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(16),),clipBehavior:Clip.antiAlias,),// ... 其余与亮色主题一致);}注意暗色主题用Brightness.dark——ColorScheme.fromSeed会基于这个参数自动调整色调的明暗层级。同一个色值#4DB6AC在 HCT 色彩空间中的表现会因亮度不同而变化。在组件中使用 ColorScheme在具体组件中通过Theme.of(context).colorScheme获取颜色classMemoCardextendsStatelessWidget{finalMemomemo;overrideWidgetbuild(BuildContextcontext){finalcolorsTheme.of(context).colorScheme;returnCard(color:colors.surface,child:ListTile(title:Text(memo.title,style:TextStyle(color:colors.onSurface)),subtitle:Text(memo.content,style:TextStyle(color:colors.onSurfaceVariant)),trailing:Icon(memo.isPinned?Icons.push_pin:null,color:colors.primary,),),);}}常用 colorScheme 属性速查属性用途primary主色用于高亮交互元素onPrimary主色上的文字颜色primaryContainer主色的容器背景比主色浅secondary次要色surface卡片、AppBar 等表面色onSurface表面上的文字颜色onSurfaceVariant表面上的次要文字灰色调error错误提示色surfaceTint表面色调关键规则color和onColor成对使用。如果背景是surface文字就是onSurface。这个命名约定在 Material 3 的文档中被严格执行。ColorScheme 的 HCT 色彩空间ColorScheme.fromSeed使用 HCT 而非传统的 HSL/RGB 色彩空间。HCT 的三个维度Hue色相与 HSL 的色相相同Chroma色度/饱和度颜色的鲜艳程度Tone亮度从 0黑到 100白HCT 的关键优势是感知均匀度——在 RGB 空间中看起来亮度一致的两个颜色在人眼感知下可能分别偏亮和偏暗。HCT 修正了这个问题让生成的色调在感知上真正一致。对开发者来说这意味着ColorScheme.fromSeed生成的色板在所有亮度层级下都具有足够的 WCAG 对比度无需手动调整。Material 3 的颜色生成算法会自动保证可访问性。皮肤页面SkinPage应用预留了一个皮肤设置页面允许用户在亮色/暗色/跟随系统之间手动切换。核心代码classSkinPageextendsStatelessWidget{overrideWidgetbuild(BuildContextcontext){finalcurrentModecontext.watchThemeProvider().themeMode;returnScaffold(appBar:AppBar(title:constText(主题设置)),body:ListView(children:[RadioListTileThemeMode(title:constText(浅色模式),value:ThemeMode.light,groupValue:currentMode,onChanged:(mode)context.readThemeProvider().setThemeMode(mode!),),RadioListTileThemeMode(title:constText(深色模式),value:ThemeMode.dark,groupValue:currentMode,onChanged:(mode)context.readThemeProvider().setThemeMode(mode!),),RadioListTileThemeMode(title:constText(跟随系统),value:ThemeMode.system,groupValue:currentMode,onChanged:(mode)context.readThemeProvider().setThemeMode(mode!),),],),);}}实现用户手动切换需要将ThemeMode提升为一个可持久化的状态classThemeProviderextendsChangeNotifier{ThemeMode_themeModeThemeMode.system;ThemeModegetthemeMode_themeMode;FuturevoidloadThemeMode()async{// 从文件/SharedPreferences 读取用户偏好finalsavedawait_loadFromPrefs(theme_mode);if(saved!null){_themeModeThemeMode.values[savedasint];notifyListeners();}}FuturevoidsetThemeMode(ThemeModemode)async{_themeModemode;await_saveToPrefs(theme_mode,mode.index);notifyListeners();}}鸿蒙兼容性Material 3 的主题系统完全在 Flutter 框架层实现。ColorScheme.fromSeed的 HCT 算法和ThemeMode.system的亮暗检测都在 Flutter 引擎中完成。ThemeMode.system在鸿蒙上依赖ohos/flutter_ohos引擎向 Flutter 报告系统亮暗模式。如果鸿蒙设备上的系统亮暗模式检测有问题可以通过MediaQuery.platformBrightnessOf(context)查看引擎返回的实际值或在 Texture 中手动设置MediaQuery的platformBrightness。总结Material 3 种子色主题系统让应用配色从手动拼凑 12 个色值简化为选一个种子色ColorScheme.fromSeed(seedColor: Color(0xFF4DB6AC))一行代码生成 30 色调亮色 暗色两个 ThemeDataBrightness.light和Brightness.dark控制色调层级ThemeMode.system自动跟随系统亮暗设置HCT 色彩空间保证感知均匀度和无障碍对比度完整项目代码见todo_flutter_harmony