Content

flutter中如何使用和扩展ThemeData实现详解(flutter for web)这样也行?

文章摘要

这篇文章详细介绍了如何在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使用方法总结