2、全局主题配置:在 MaterialApp 中配置全局主题,实现一键换肤的基础架构
好,咱们直接进入正题。上一章我们聊了为什么要做主题定制,这一章就来动手搭架子。
全局主题配置,说白了就是给整个 App 定一套「皮肤规则」。你想想看,如果每个页面都单独写颜色、写字体,那改起来得多痛苦?我早期做项目时就吃过这个亏——客户说「把主色调从蓝色改成绿色」,我改了整整两天,还漏了好几个按钮。嗯,从那以后,我再也不敢不搞全局主题了。
2.1 核心思路:ThemeData 就是你的调色盘
Flutter 里做主题,核心就一个类——ThemeData。它就像一个大容器,装着所有组件的样式:颜色、字体、形状、阴影……你只需要在 MaterialApp 里配置一次,整个 App 都能共享。
我个人习惯把主题配置拆成三块:
- 颜色体系:主色、辅色、背景色、文字色
- 文字体系:标题、正文、小字的字号和字重
- 组件样式:按钮、输入框、卡片等具体控件的默认样式
这样拆的好处是——改颜色不影响文字,改文字不影响按钮,各管各的。
2.2 基础代码:在 MaterialApp 里挂主题
先看一个最基础的例子。你只需要在 MaterialApp 的 theme 参数里传入一个 ThemeData 对象:
MaterialApp(
title: '我的 App',
theme: ThemeData(
// 主色调
primarySwatch: Colors.blue,
// 视觉密度(紧凑还是宽松)
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
这样写没问题,但太简单了。实际项目中,我们往往需要更精细的控制。我建议你直接定义 ThemeData 的各个属性,而不是只靠 primarySwatch:
ThemeData(
// 颜色
primaryColor: Color(0xFF2196F3),
colorScheme: ColorScheme.fromSeed(
seedColor: Color(0xFF2196F3),
brightness: Brightness.light,
),
// 文字
textTheme: TextTheme(
headlineLarge: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
bodyLarge: TextStyle(fontSize: 16, color: Colors.black87),
),
// 组件
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Color(0xFF2196F3)),
foregroundColor: MaterialStateProperty.all(Colors.white),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
),
),
);
colorScheme 替代 primarySwatch。前者更灵活,支持亮色/暗色模式切换,后者是旧 API,迟早要淘汰。
2.3 一键换肤的核心:多主题切换
一键换肤,说白了就是准备多套 ThemeData,然后动态切换。我常用的做法是定义一个主题数据类:
class AppTheme {
static ThemeData lightTheme = ThemeData(
brightness: Brightness.light,
primaryColor: Color(0xFF2196F3),
colorScheme: ColorScheme.light(
primary: Color(0xFF2196F3),
secondary: Color(0xFF03DAC6),
),
// ... 其他配置
);
static ThemeData darkTheme = ThemeData(
brightness: Brightness.dark,
primaryColor: Color(0xFF90CAF9),
colorScheme: ColorScheme.dark(
primary: Color(0xFF90CAF9),
secondary: Color(0xFF03DAC6),
),
// ... 其他配置
);
}
然后在 MaterialApp 里根据状态切换:
MaterialApp(
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: ThemeMode.system, // 跟随系统,也可以手动指定
home: MyHomePage(),
);
这里有个坑——themeMode 有三个值:light、dark、system。如果你只想要亮色,就设 ThemeMode.light;想要暗色就设 ThemeMode.dark。我曾经犯过一个错:只配了 theme 没配 darkTheme,结果用户手机切到暗色模式时,App 直接崩了……
theme 和 darkTheme,一定要确保 themeMode 设置正确。否则系统自动切换时,可能找不到对应的主题配置。
2.4 进阶:用 InheritedWidget 实现全局主题状态
如果你需要更复杂的换肤逻辑(比如用户自定义颜色),光靠 MaterialApp 的 theme 参数就不够了。这时候我推荐用 InheritedWidget 或者 Provider 来管理主题状态。
这里给一个简单的 InheritedWidget 示例:
class ThemeProvider extends InheritedWidget {
final ThemeData themeData;
final bool isDarkMode;
const ThemeProvider({
Key? key,
required this.themeData,
required this.isDarkMode,
required Widget child,
}) : super(key: key, child: child);
static ThemeProvider? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ThemeProvider>();
}
@override
bool updateShouldNotify(ThemeProvider oldWidget) {
return themeData != oldWidget.themeData || isDarkMode != oldWidget.isDarkMode;
}
}
然后在顶层包裹:
ThemeProvider(
themeData: currentTheme,
isDarkMode: _isDarkMode,
child: MaterialApp(
theme: currentTheme,
home: MyHomePage(),
),
);
这样,任何子组件都能通过 ThemeProvider.of(context) 拿到当前主题,甚至能触发重建。
核心要点总结:
ThemeData是全局主题的基石,所有样式都从这里来- 用
colorScheme替代primarySwatch,更现代更灵活 - 亮色/暗色模式通过
theme+darkTheme+themeMode三件套实现 - 复杂场景用
InheritedWidget或状态管理库来驱动主题切换
嗯,这一章的内容就到这。下一章我们会深入 ThemeData 的各个属性,手把手教你配出一套漂亮又实用的主题。到时候我会分享一些我在设计系统里踩过的坑,比如「为什么我的按钮颜色死活改不了」——你猜怎么着?八成是 ThemeData 的优先级没搞明白。