
设计理念:遵循 Material 3
项目采用 Material 3 的主题体系,核心理念是"从单一种子色生成完整的设计系统"。Material 3 引入了基于 HCT(Hue-Chroma-Tone,色调-色度-明度)的颜色系统,只需提供一个种子色,框架会自动生成 Primary、Secondary、Tertiary、Error、Surface 五个颜色组,每个组包含多个明度变体。
AppTheme 类
dart
import 'package:flutter/material.dart';
class AppTheme {
static const _seedColor = Color(0xFF1A73E8); // Google Blue
static ThemeData get light => _buildTheme(Brightness.light);
static ThemeData get dark => _buildTheme(Brightness.dark);
static ThemeData _buildTheme(Brightness brightness) {
final colorScheme = ColorScheme.fromSeed(
seedColor: _seedColor,
brightness: brightness,
);
return ThemeData(
colorScheme: colorScheme,
useMaterial3: true,
// 组件主题覆盖
appBarTheme: const AppBarTheme(
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: const EdgeInsets.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 主题
dart
appBarTheme: const AppBarTheme(
centerTitle: false, // 标题左对齐(而非居中)
elevation: 0, // 无阴影(扁平设计)
scrolledUnderElevation: 1, // 滚动时显示 1px 阴影
),
centerTitle: false 是 Material 3 的默认值,左对齐标题适合内容密度高的应用。scrolledUnderElevation: 1 在用户滚动内容时在 AppBar 底部添加细微阴影,提供视觉层次------内容滚动到 AppBar 下面时,阴影分离了两者。
Card 主题
dart
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 主题

dart
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
isDense: true,
),
OutlineInputBorder 是 Material 3 推荐的输入框样式(对比 Material 2 的 UnderlineInputBorder 线条样式)。12px 圆角与 Card 保持一致。isDense: true 减小输入框的默认高度,适合移动端紧凑布局。
FilledButton 主题
dart
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
FilledButton 是 Material 3 的主要按钮样式,使用 Primary 色填充。8px 圆角(比 Card 的 12px 略小)是 Material 3 按钮的推荐圆角值。
在 MaterialApp 中使用主题
dart
class AtomGitApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [...],
child: MaterialApp(
title: 'AtomGit',
theme: AppTheme.light,
darkTheme: AppTheme.dark,
themeMode: ThemeMode.system, // 跟随系统设置
home: const MainShell(),
onGenerateRoute: AtomGitApp.generateRoute,
),
);
}
}
themeMode: ThemeMode.system 让主题自动跟随系统:
- 系统浅色模式 → AppTheme.light
- 系统深色模式 → AppTheme.dark
Flutter 在系统主题切换时会自动重建 MaterialApp,无需应用代码手动处理。这是在 ThemeData 级别实现的------两个 ThemeData 对象中的颜色值不同,MaterialApp 自动选择。
组件中消费主题
所有组件通过 Theme.of(context) 获取当前主题,而不是硬编码颜色值:
dart
// 获取主题色
final primaryColor = Theme.of(context).colorScheme.primary;
// 获取错误色
final errorColor = Theme.of(context).colorScheme.error;
// 获取 Surface 容器色(代码块背景、头像占位)
final containerColor = Theme.of(context)
.colorScheme.surfaceContainerHighest;
// 获取文字样式
final titleStyle = Theme.of(context).textTheme.titleMedium;
final bodyStyle = Theme.of(context).textTheme.bodySmall;
为什么不能硬编码颜色
dart
// 不好:切换深色模式后文字不可见
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 常量和应用配置
dart
class ApiConstants {
static const String baseUrl =
'https://api.atomgit.com/api/v5';
static const String authorizeUrl =
'https://api.atomgit.com/login/oauth/authorize';
static const String tokenUrl =
'https://api.atomgit.com/login/oauth/access_token';
static const String redirectUri =
'atomgit://oauth/callback';
static const String apiVersion = '2023-02-21';
static const String scope = 'repo user';
static const int pageSize = 30;
static const int rateLimitAuthenticated = 5000;
static const int rateLimitUnauthenticated = 60;
}
API 相关常量集中在一个类中。这遵循"单一数据源"原则------如果 API 地址变更,只需修改一处。
主题扩展
未来可以为应用添加应用内主题切换(不依赖系统设置)。实现思路:
dart
enum AppThemeMode { light, dark, system }
class ThemeProvider extends ChangeNotifier {
AppThemeMode _mode = AppThemeMode.system;
ThemeMode get themeMode => switch (_mode) {
AppThemeMode.light => ThemeMode.light,
AppThemeMode.dark => ThemeMode.dark,
AppThemeMode.system => ThemeMode.system,
};
void setMode(AppThemeMode mode) {
_mode = mode;
notifyListeners();
}
}
然后在 MaterialApp 中:
dart
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),形成清晰的视觉层次。