设计理念:遵循 Material 3
项目采用 Material 3 的主题体系,核心理念是"从单一种子色生成完整的设计系统"。Material 3 引入了基于 HCT(Hue-Chroma-Tone,色调-色度-明度)的颜色系统,只需提供一个种子色,框架会自动生成 Primary、Secondary、Tertiary、Error、Surface 五个颜色组,每个组包含多个明度变体。
AppTheme 类
import'package:flutter/material.dart';classAppTheme{staticconst_seedColor=Color(0xFF1A73E8);// Google BluestaticThemeDatagetlight=>_buildTheme(Brightness.light);staticThemeDatagetdark=>_buildTheme(Brightness.dark);staticThemeData_buildTheme(Brightnessbrightness){finalcolorScheme=ColorScheme.fromSeed(seedColor:_seedColor,brightness:brightness,);returnThemeData(colorScheme:colorScheme,useMaterial3:true,// 组件主题覆盖appBarTheme:constAppBarTheme(centerTitle:false,elevation:0,scrolledUnderElevation:1,),cardTheme:CardTheme(elevation:1,shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(12),),clipBehavior:Clip.antiAlias,),inputDecorationTheme:InputDecorationTheme(border:OutlineInputBorder(borderRadius:BorderRadius.circular(12),),contentPadding:constEdgeInsets.symmetric(horizontal:16,vertical:12,),isDense:true,),filledButtonTheme:FilledButtonThemeData(style:FilledButton.styleFrom(shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(8),),),),);}}种子色的选择
#1A73E8(Google Blue)被选为种子色。这个颜色在蓝色系中具有良好的饱和度和对比度,Material 3 的ColorScheme.fromSeed会基于它生成完整的调色板。如果后续需要更换品牌色,只需修改这一个常量。
Brightness 枚举的使用
_buildTheme接收Brightness枚举并根据其值生成对应的 ColorScheme。Material 3 的ColorScheme.fromSeed在接收brightness: Brightness.dark时,会自动调整所有颜色的明度——Primary 色会调亮以在深色背景上有足够对比度,Surface 色会变暗。
组件主题覆盖的详细说明
全局的ThemeData是组件样式的最高优先级。通过ThemeData的组件属性覆盖,可以确保应用中所有同类组件保持一致的视觉效果,无需在每个使用点重复设置样式。
AppBar 主题
appBarTheme:constAppBarTheme(centerTitle:false,// 标题左对齐(而非居中)elevation:0,// 无阴影(扁平设计)scrolledUnderElevation:1,// 滚动时显示 1px 阴影),centerTitle: false是 Material 3 的默认值,左对齐标题适合内容密度高的应用。scrolledUnderElevation: 1在用户滚动内容时在 AppBar 底部添加细微阴影,提供视觉层次——内容滚动到 AppBar 下面时,阴影分离了两者。
Card 主题
cardTheme:CardTheme(elevation:1,// 轻微阴影shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(12),// 12px 圆角),clipBehavior:Clip.antiAlias,// 抗锯齿裁剪),elevation: 1创建非常轻微的阴影——刚好能让 Card 从背景中浮出来,但不会有 Material 2 那种厚重的投影感。12px 的圆角属于 Material 3 的"中等圆角"风格。
clipBehavior: Clip.antiAlias确保 Card 的子 Widget 被裁剪到圆角边界内。没有这个设置,子 Widget(如 InkWell 的水波纹)可能超出圆角范围,产生视觉瑕疵。
InputDecoration 主题
inputDecorationTheme:InputDecorationTheme(border:OutlineInputBorder(borderRadius:BorderRadius.circular(12),),contentPadding:constEdgeInsets.symmetric(horizontal:16,vertical:12,),isDense:true,),OutlineInputBorder是 Material 3 推荐的输入框样式(对比 Material 2 的 UnderlineInputBorder 线条样式)。12px 圆角与 Card 保持一致。isDense: true减小输入框的默认高度,适合移动端紧凑布局。
FilledButton 主题
filledButtonTheme:FilledButtonThemeData(style:FilledButton.styleFrom(shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(8),),),),FilledButton是 Material 3 的主要按钮样式,使用 Primary 色填充。8px 圆角(比 Card 的 12px 略小)是 Material 3 按钮的推荐圆角值。
在 MaterialApp 中使用主题
classAtomGitAppextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnMultiProvider(providers:[...],child:MaterialApp(title:'AtomGit',theme:AppTheme.light,darkTheme:AppTheme.dark,themeMode:ThemeMode.system,// 跟随系统设置home:constMainShell(),onGenerateRoute:AtomGitApp.generateRoute,),);}}themeMode: ThemeMode.system让主题自动跟随系统:
- 系统浅色模式 → AppTheme.light
- 系统深色模式 → AppTheme.dark
Flutter 在系统主题切换时会自动重建 MaterialApp,无需应用代码手动处理。这是在ThemeData级别实现的——两个ThemeData对象中的颜色值不同,MaterialApp 自动选择。
组件中消费主题
所有组件通过Theme.of(context)获取当前主题,而不是硬编码颜色值:
// 获取主题色finalprimaryColor=Theme.of(context).colorScheme.primary;// 获取错误色finalerrorColor=Theme.of(context).colorScheme.error;// 获取 Surface 容器色(代码块背景、头像占位)finalcontainerColor=Theme.of(context).colorScheme.surfaceContainerHighest;// 获取文字样式finaltitleStyle=Theme.of(context).textTheme.titleMedium;finalbodyStyle=Theme.of(context).textTheme.bodySmall;为什么不能硬编码颜色
// 不好:切换深色模式后文字不可见Text('Hello',style:TextStyle(color:Colors.black));// 好:自动适配深色/浅色Text('Hello',style:TextStyle(color:Theme.of(context).colorScheme.onSurface));硬编码颜色只在一个主题下是正确的。Theme.of(context)获取的颜色是当前有效主题的值,自动适配系统切换。
ColorScheme 的关键令牌
Material 3 的ColorScheme提供了丰富的颜色令牌。项目中使用频率最高的:
| 令牌 | 浅色模式示例 | 深色模式示例 | 用途 |
|---|---|---|---|
primary | 亮蓝 | 淡蓝 | 主题色,用于按钮、选中态、Tab 指示器 |
onPrimary | 白色 | 深蓝黑 | Primary 色上的文字色 |
primaryContainer | 淡蓝 | 深蓝 | 展示 primary 的背景容器 |
onPrimaryContainer | 深蓝 | 淡蓝 | 容器上的文字色 |
secondary | 蓝灰 | 浅蓝灰 | 次要元素(FilterChip) |
error | 红色 | 浅红 | 错误状态图标和文字 |
surface | 近白 | 深灰 | 页面背景 |
surfaceContainerHighest | 浅灰 | 深灰 | 最突出的表面容器(代码块背景) |
onSurface | 深灰(近黑) | 近白 | 主要文字色 |
onSurfaceVariant | 中灰 | 浅灰 | 次要文字色 |
Material 3 ColorScheme.fromSeed 的原理
ColorScheme.fromSeed使用 Material 3 的 HCT 色彩系统:
- 输入:一个种子色(
#1A73E8)和一个亮度模式 - 色调(Hue)分析:从种子色提取色调值
- 色度(Chroma)分配:为 Primary 分配最高色度(鲜艳),Secondary 降低色度(柔和),Tertiary 取互补色调
- 明度(Tone)调整:根据 brightness 参数为每个颜色生成不同明度的变体
- 对比度保证:
OnXxx颜色自动选择与对应Xxx色具有足够对比度的明度值(满足 WCAG 无障碍标准)
深色模式的颜色适配
Material 3 的深色模式不是简单地反转颜色,而是精心调整色调和明度:
- Primary 变淡:在深色背景上,
primary变得更亮以确保对比度 - Surface 变暗:背景色从浅灰变为深灰,减少屏幕发光量
- Container 颜色反转:浅色模式下 Container 是主色的淡化版(白底上带淡色),深色模式下 Container 是主色的加深版(黑底上带暗色)
- OnSurface 变亮:文字色从深色变为浅色,保证在暗背景上的可读性
项目中不需要为深色模式写任何额外的颜色代码——所有颜色通过ColorScheme引用,自动获得适配。
API 常量和应用配置
classApiConstants{staticconstStringbaseUrl='https://api.atomgit.com/api/v5';staticconstStringauthorizeUrl='https://api.atomgit.com/login/oauth/authorize';staticconstStringtokenUrl='https://api.atomgit.com/login/oauth/access_token';staticconstStringredirectUri='atomgit://oauth/callback';staticconstStringapiVersion='2023-02-21';staticconstStringscope='repo user';staticconstint pageSize=30;staticconstint rateLimitAuthenticated=5000;staticconstint rateLimitUnauthenticated=60;}API 相关常量集中在一个类中。这遵循"单一数据源"原则——如果 API 地址变更,只需修改一处。
主题扩展
未来可以为应用添加应用内主题切换(不依赖系统设置)。实现思路:
enumAppThemeMode{light,dark,system}classThemeProviderextendsChangeNotifier{AppThemeMode_mode=AppThemeMode.system;ThemeModegetthemeMode=>switch(_mode){AppThemeMode.light=>ThemeMode.light,AppThemeMode.dark=>ThemeMode.dark,AppThemeMode.system=>ThemeMode.system,};voidsetMode(AppThemeModemode){_mode=mode;notifyListeners();}}然后在MaterialApp中:
MaterialApp(themeMode:context.watch<ThemeProvider>().themeMode,// ...)使用主题的最佳实践
几个贯穿整个项目的主题使用原则:
永远使用
Theme.of(context),不要缓存主题引用。系统主题切换时缓存的引用会过时。使用语义颜色令牌,而非低级颜色值。
colorScheme.error好于Colors.red,因为 error 色在深色模式下会自动调节明度。使用
textTheme的样式层级,而非自定义 fontSize。textTheme.titleMedium好于fontSize: 16,因为字体大小适配无障碍设置。Card 和 InputDecoration 的圆角保持一致(12px),FilledButton 略小(8px),形成清晰的视觉层次。