文章摘要
这篇文章详细介绍了如何在Flutter项目中扩展ThemeData以实现主题的扩展配置和一键切换功能。以下是总结和步骤:
---
### 总结
这篇文章通过一个完整的Flutter项目展示了如何扩展ThemeData来实现以下功能:
1. **扩展主题数据**:定义新的字段(如自定义颜色`connerColor`)并在`ThemeData`中添加。
2. **创建主题扩展类**:定义`ThemeItem`类包装扩展数据,并实现`ThemeConfig`接口以监听主题变化。
3. **使用Provider监听主题变化**:创建`AppInfoProvider`以通知主题切换,并在 listeners中处理切换逻辑。
4. **创建主题仓库**:定义`ThemeStore`存储不同主题的实例,并初始化主题。
5. **实现一键切换功能**:通过`initTheme`方法切换主题,并在`Provider.of`中使用`setTheme`方法切换主题。
---
### 实现步骤
以下是实现上述功能的详细步骤:
#### 1. 定义扩展字段
在`ThemeData`内部定义新的字段,并将其扩展为可读写。
```dart
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),
),
// 添加新的样式字段
connerColor: null,
// 其他样式字段
...
),
// 其他样式配置
),
)
```
#### 2. 创建扩展类`ThemeItem`
定义一个新的类`ThemeItem`,包装扩展数据,并实现`ThemeConfig`接口。
```dart
abstract class ThemeConfig {
static late ThemeItem _currentTheme;
static ThemeEnum get currentTheme() {
return _currentTheme?.themeEnum;
}
static Color? get connerColor() {
return _currentTheme?.connerColor;
}
static void initTheme(ThemeItem theme) {
_currentTheme = theme;
}
}
class ThemeItem {
final ThemeEnum themeEnum;
final ThemeData themeData;
final Color connerColor;
ThemeItem(this.themeEnum, this.themeData, this.connerColor);
@override
ThemeEnum currentTheme() {
return themeEnum;
}
@override
ThemeData currentThemeData() {
return themeData;
}
@override
Color currentConnerColor() {
return connerColor;
}
void setCurrentTheme(ThemeItem newTheme) {
_currentTheme = newTheme;
}
}
```
#### 3. 创建`ThemeStore`
定义一个抽象类`ThemeStore`,存储不同主题的实例。
```dart
abstract class ThemeStore {
const List<ThemeItem> themes = [
ThemeItem(
themeEnum: ThemeEnum.yellow,
themeData: ThemeData(
primaryColor: Colors.yellow,
backgroundColor: Colors.yellow,
),
connerColor: Colors.blue,
),
ThemeItem(
themeEnum: ThemeEnum.red,
themeData: ThemeData(
primaryColor: Colors.red,
backgroundColor: Colors.red,
),
connerColor: Colors.green,
),
];
}
```
#### 4. 创建`AppInfoProvider`
使用Provider监听主题变化,并在 listeners中切换主题。
```dart
class AppInfoProvider with ChangeNotifier {
ThemeConfig currentTheme;
ThemeItem currentThemeItem;
void setTheme(ThemeItem theme) {
currentThemeItem = theme;
notifyListeners();
}
@override
ThemeConfig get currentTheme() {
return currentThemeItem.themeEnum;
}
}
```
#### 5. 实现一键切换功能
在`Provider.of`中使用`setTheme`方法实现一键切换。
```dart
Provider.of<AppInfoProvider, value: void, listen: false>.setTheme(
ThemeStore.themes.first;
)
```
#### 6. 创建主题仓库
定义一个主题仓库,存储不同主题的实例,并实现`Provider`接口。
```dart
class ThemePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
listeners: ChangeNotifierProvider(),
child: Consumer<ChangeNotifierProvider, value: void, builder: (context, appInfo) => {
return MaterialApp(
theme: appInfo.currentTheme,
home: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
BodyWidget(),
SizedBox(
height: 30,
),
ThemePageButton(),
],
),
);
}),
);
}
}
```
#### 7. 实现主题切换
通过`initTheme`方法切换主题,并在`Provider.of`中使用`setTheme`方法实现一键切换。
```dart
void ThemeSwitch(String newThemeEnum) {
ThemeStore.themes.forEach((theme) {
if (newThemeEnum == theme.themeEnum) {
themeData = theme.themeData;
connerColor = theme.connerColor;
}
});
}
Provider.of<ThemePageProvider, value: void, listen: false>.setTheme(
ThemeStore.themes.first;
).whenChanged((value) {
ThemeSwitch(value);
});
```
---
### 总结
通过以上步骤,可以在Flutter项目中扩展ThemeData,实现主题的扩展配置和一键切换功能。关键在于定义新的字段、创建扩展类、使用Provider监听主题变化,并在 listeners中实现所需的切换逻辑。
目录前言Theme 的基本使用方式1. Theme 的注册2. 读取 ThemeData 里的配置:小技巧介绍ThemeData 内置字段不够用,如何扩展?如何实现一键换肤1. 首先在 yaml 新增引入 provider2. 创建主题枚举3. ThemeData 进行一层封装处理4. 创建一个主题管理类 ThemeConfig5. 通过ThemeData进行读取保持统一6. 基于 provider 的使用7. 创建一个主题仓库,里面存放两套主题,用于演示 Demo8.完整的 demo 代码及效果
做过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类,可以让我们节省封装常量配置类(如上示例中的 ColorConfigs)的步骤。将色值、字体风格等配置内容存入ThemeData中,子控件可统一通过 读取 color、textStyle、等配置信息。
本篇通过换肤demo,介绍在flutter项目中如何使用 theme 以及如何对 themeData 进行字段扩展,实现全局的主题配置管理。
MaterialApp(
theme: myThemeData, //一个ThemeData的实例,下面提供具体代码
home: BodyWidget(),
)
我们做全局的主体配置,在 MaterialApp 中对 theme 字段进行入参赋值。示例代码中的 myThemeData 是一个 ThemeData 的实现实例,可通过 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,
);
}
:读取主题配置中的背景颜色,在 myThemeData 中进行过赋值操作:读取主题配置中键值为 bodyText1 的字体样式
小技巧介绍
通常为了便于开发阅读,我们也可以使用对 ThemeData 内属性进行重命名获取:
新建 extension_theme.dart,文件名字随意:
///用于重命名颜色属性
extension ThemeDataColorExtension on ThemeData {
Color get bgColor=> colorScheme.onBackground;
…
}
///用于重命名字体样式属性
extension ThemeDataTextStyleExtension on ThemeData {
TextStyle get bodyStyle=> textTheme.bodyText1!;
…
}
在UI页面进行引用导入使用,上面的 demo 可改为:
import https://www.jb51.net/article/extension_theme.dart
…
@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).bgColor,
child: Text(
‘hellow’,
style: Theme.of(context).bodyStyle,
);
}
从 ThemeData 的构造函数中我们可以看到,ThemeData 内置的字段是有限的。假如我们的UI设计包含的色值数量或者字体样式数量超出了 ThemeData 可供设置数量怎么办呢?
比如:我们想新增一个色值配置,名字就叫 ,我们还想保持统一,一律通过 ThemeData 来统一读取统一配置,要如何处理呢?
小编在项目里是这么做的,将 进行一层封装,以新增 为例,具体代码结合下面的一键换肤查询。
有了ThemeData作为统一管理存放配置信息后,实现一键换肤的思路就很清晰了,大致是这样的:

从上图可以看到,除了需要用于存放配置信息,我们还需要封装一个监听类用于监听选中主题发生变更,这个功能我们在下面用provider来实现。
dependencies:
provider: ^6.0.2
假设我们提供两种主题切换
///主题类型
enum ThemeEnum {
yellow,
red,
}
我们对 进行一层封装处理,添加 进行颜色字段扩展
///自定义模型,包装一下 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;
}
}
为保持统一通过进行读取,使用对新增字段进行读取扩展
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<AppInfoProvider>(context, listen: false).setTheme(themeItem);
///主题仓库
abstract class ThemeStore {
static List<ThemeItem> 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<AppInfoProvider>(
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: () {
Navigator.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: <Widget>[
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<AppInfoProvider>(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,
),
);
}
}

以上就是flutter中如何使用和扩展ThemeData实现详解的详细内容,更多关于flutter ThemeData扩展的资料请关注脚本之家其它相关文章!
您可能感兴趣的文章:flutter Bloc 更新后事件同步与异步详解使用PlatformView将?Android?控件view制作成Flutter插件Flutter 状态管理scoped model源码解读Flutter改变状态变量是否必须写在setState回调详解Flutter?绘制风车实现示例详解flutter InheritedWidget使用方法总结