发布于2026-07-22 阅读(0)
扫一扫,手机访问
做过UI开发的朋友应该都有体会:我们通常会把颜色、字体大小这类配置单独抽出来,放在一个统一的管理类里,而不是直接写在UI代码中。这样后续调整时,改个配置文件就行,不用动源码。
举个例子:
/// 存放颜色常量
abstract class ColorConfigs {
static const Color background = Color(0xFFFF6600);
static const Color textHint = Color(0xFFA0A4A7);
}
/// GOOD Container( //通过 ColorConfig 获取色值 color: ColorConfigs.background, ) /// BAD Container( //写死 color: Color(0xFFFF6600), )
Flutter 自带的 Theme 类,能帮我们省掉手动封装常量配置类的步骤。把色值、字体风格等存入 ThemeData,子控件统一通过 Theme.of(context) 读取各项配置。
这篇通过一个换肤 Demo,聊聊如何在 Flutter 项目中使用 Theme,以及如何对 ThemeData 进行字段扩展,实现全局主题配置管理。
MaterialApp( theme: myThemeData, //一个ThemeData的实例,下面提供具体代码 home: BodyWidget(), )
做全局主题配置,只需要在 MaterialApp 的 theme 字段传入一个 ThemeData 实例。myThemeData 就是实现实例,可以通过 ThemeData 的构造方法查看它支持保存的主题和样式信息,按需赋值即可。
下面是一份实际项目中用到的 ThemeData 配置,定义了各种状态颜色、字体样式,供参考:
myThemeData:
val myThemeData = ThemeData(
primaryColor: Colors.white,
disabledColor: const Color(0xffcbced0),
backgroundColor: const Color(0xfff3f4f5),
hintColor: const Color(0xffe2e5e7),
errorColor: const Color(0xffe21a1a),
highlightColor: const Color(0xffa7d500),
shadowColor: const Color(0xffa0a4a7),
selectedRowColor: const Color(0xfff3f4f5),
colorScheme: const ColorScheme.light(
primary: Colors.white,
secondary: Color(0xffa7d500),
background: Color(0xfff3f4f5),
error: Color(0xffe21a1a),
onPrimary: Color(0xff242524),
onError: Colors.white,
onBackground: Color(0xffe2e5e7),
onSecondary: Color(0xff707275),
),
textTheme: TextTheme(
headline1: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.bold,
color: const Color(0xff242524),
),
headline2: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: const Color(0xff242524),
),
...中间省略 healin3 ~ headline5,只是配置不一样
headline6: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
color: const Color(0xff707275),
),
subtitle1: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w500,
color: const Color(0xff242524),
),
subtitle2: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w500,
color: const Color(0xff707275),
),
bodyText1: TextStyle(
fontSize: 11.sp,
fontWeight: FontWeight.normal,
color: const Color(0xff242524),
),
bodyText2: TextStyle(
fontSize: 11.sp,
fontWeight: FontWeight.normal,
color: const Color(0xff242524),
),
),
)
@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).backgroundColor,
child: Text(
'hellow',
style: Theme.of(context).headline).bodyText1,
);
}
Theme.of(context).backgroundColor:读取背景颜色,在 myThemeData 中已赋值。Theme.of(context).headline).bodyText1:读取键值为 bodyText1 的字体样式。为方便阅读,可以用 extension 对 ThemeData 内的属性重命名获取:
///用于重命名颜色属性
extension ThemeDataColorExtension on ThemeData {
Color get bgColor => colorScheme.onBackground;
...
}
///用于重命名字体样式属性
extension ThemeDataTextStyleExtension on ThemeData {
TextStyle get bodyStyle => textTheme.bodyText1!;
...
}
在 UI 页面导入并引用,上面的 demo 可以改为:
import ./extension_theme.dart
...
@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).bgColor,
child: Text(
'hellow',
style: Theme.of(context).bodyStyle,
);
}
从 ThemeData 的构造函数可以看到,它内置的字段是有限的。如果 UI 设计包含的色值或字体样式数量超出了 ThemeData 的可设置范围,该怎么办?
比如想新增一个色值配置,叫 connerColor,并且希望保持统一,全部通过 ThemeData 来读取和配置。怎么处理?
常见的做法是:将 ThemeData 封装一层,以新增 connerColor 为例。具体代码结合下面的一键换肤一起看。
有了 ThemeData 作为统一的管理配置后,实现一键换肤的思路就很清晰了:

除了 ThemeData 存放配置信息,还需要一个监听类来通知主题变更。这里用 provider 来实现。
dependencies: provider: ^6.0.2
假设提供两种主题切换:
///主题类型
enum ThemeEnum {
yellow,
red,
}
对 ThemeData 封装一层,添加 connerColor 扩展字段:
///自定义模型,包装一下 themeData
class ThemeItem {
final ThemeEnum themeEnum;
final ThemeData themeData;
// 扩展一个字段,用于表示自定义色值
final Color connerColor;
ThemeItem(
this.themeEnum,
this.themeData, {
required this.connerColor,
});
}
abstract class ThemeConfig {
///记录当前选中主题
static late ThemeItem _currentTheme;
static ThemeData get currentThemeData => _currentTheme.themeData;
static ThemeEnum get currentTheme => _currentTheme.themeEnum;
///提供获取扩展的色值
static Color? get connerColor => _currentTheme.connerColor;
///设置选中主题,提供外部调用,更换当前主题
static void initTheme(ThemeItem theme) {
_currentTheme = theme;
}
}
为了保持统一,仍然通过 ThemeData 读取,使用 extension 对新增字段 connerColor 进行扩展:
extension ExTheme on ThemeData {
///扩展获取自定义色值
Color get connerColor => ThemeConfig.connerColor!;
}
添加一个工具类,用于通知主题变更:
class AppInfoProvider with ChangeNotifier {
ThemeData get currentTheme => ThemeConfig.currentThemeData;
///切换主题
setTheme(ThemeItem theme) {
ThemeConfig.initTheme(theme);
notifyListeners();
}
}
切换主题时直接调用:
Provider.of(context, listen: false).setTheme(themeItem);
///主题仓库
abstract class ThemeStore {
static List themes = [
//红色主题
ThemeItem(
ThemeEnum.yellow,
ThemeData(
primaryColor: Colors.yellow,
backgroundColor: Colors.yellow,
),
connerColor: Colors.blue,
),
//黄色主题
ThemeItem(
ThemeEnum.red,
ThemeData(
primaryColor: Colors.red,
backgroundColor: Colors.red,
),
connerColor: Colors.green,
),
];
}
main.dart
void main() {
///初始化主题
ThemeConfig.initTheme(
ThemeStore.themes.first,
);
runApp(const Material(
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: AppInfoProvider()),
],
child: Consumer(
builder: (context, appInfo, _) {
return MaterialApp(
theme: appInfo.currentTheme,
home: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: const [
BodyWidget(),
SizedBox(
height: 30,
),
_ThemePageButton(),
],
),
);
},
),
);
}
}
class _ThemePageButton extends StatelessWidget {
const _ThemePageButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: () {
Na vigator.push(
context,
MaterialPageRoute(
builder: (context) => const ThemeSetWidget(),
),
);
},
child: const Text('打开主题设置页面'),
);
}
}
body_widget
class BodyWidget extends StatelessWidget {
const BodyWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 300,
width: 300,
//读取标准色值
color: Theme.of(context).backgroundColor,
child: Center(
child: Container(
height: 100,
width: 150,
//读取自定义色值
color: Theme.of(context).connerColor,
),
),
);
}
}
两个方块:外层方块读取 ThemeData 标准字段色值,内层方块读取扩展字段色值,统一通过 ThemeData 读取。
theme_set_widget
extension ExThemeEnum on ThemeEnum {
Color get value {
switch (this) {
case ThemeEnum.yellow:
return Colors.yellow;
case ThemeEnum.red:
return Colors.red;
}
}
}
///主题选择页面
class ThemeSetWidget extends StatelessWidget {
const ThemeSetWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("颜色主题"),
backgroundColor: Theme.of(context).backgroundColor,
),
body: ExpansionTile(
leading: const Icon(Icons.color_lens),
title: const Text('颜色主题'),
initiallyExpanded: true,
children: [
Padding(
padding: const EdgeInsets.only(
left: 10,
right: 10,
bottom: 10,
),
child: Wrap(
spacing: 8,
runSpacing: 8,
children: ThemeStore.themes
.map((e) => _createItemWidget(context, e))
.toList(),
),
)
],
),
);
}
Widget _createItemWidget(
BuildContext context,
ThemeItem theme,
) {
return InkWell(
onTap: () {
Provider.of(context, listen: false).setTheme(theme);
},
child: Container(
width: 40,
height: 40,
color: theme.themeEnum.value,
child: ThemeConfig.currentTheme == theme.themeEnum
? const Icon(
Icons.done,
color: Colors.white,
)
: null,
),
);
}
}

售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8