flutter中如何使用和擴(kuò)展ThemeData實(shí)現(xiàn)詳解
前言
做過(guò)UI開發(fā)的同學(xué)都知道,在開發(fā)中我們通常會(huì)將 文字大小、色值 等內(nèi)容放在配置文件中,通過(guò)統(tǒng)一的管理類來(lái)讀取(嚴(yán)禁在UI代碼中寫死)。以便后續(xù)調(diào)整時(shí)不用修改源碼,只需要修改配置文件即可。
例如這樣:
- 定義常量存放
/// 存放顏色常量 abstract class ColorConfigs { static const Color background = Color(0xFFFF6600); static const Color textHint = Color(0xFFA0A4A7); }
- 通過(guò)統(tǒng)一獲取
/// GOOD Container( //通過(guò) ColorConfig 獲取色值 color: ColorConfigs.background, ) /// BAD Container( //寫死 color: Color(0xFFFF6600), )
Flutter為我們提供了Theme類,可以讓我們節(jié)省封裝常量配置類(如上示例中的 ColorConfigs)的步驟。將色值、字體風(fēng)格等配置內(nèi)容存入ThemeData中,子控件可統(tǒng)一通過(guò) Theme.of(context)
讀取 color、textStyle、等配置信息。
本篇通過(guò)換膚demo,介紹在flutter項(xiàng)目中如何使用 theme 以及如何對(duì) themeData 進(jìn)行字段擴(kuò)展,實(shí)現(xiàn)全局的主題配置管理。
Theme 的基本使用方式
1. Theme 的注冊(cè)
MaterialApp( theme: myThemeData, //一個(gè)ThemeData的實(shí)例,下面提供具體代碼 home: BodyWidget(), )
我們做全局的主體配置,在 MaterialApp 中對(duì) theme 字段進(jìn)行入?yún)①x值。示例代碼中的 myThemeData 是一個(gè) ThemeData 的實(shí)現(xiàn)實(shí)例,可通過(guò) ThemeData 的構(gòu)造方法來(lái)查看其可供保存的主體及樣式信息,按照各自所需進(jìn)行參數(shù)賦值。
下面是小編在自己項(xiàng)目中用到的ThemeData配置項(xiàng),定義了各種狀態(tài)顏色、字體樣式、可供參考:
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), ), ), )
2. 讀取 ThemeData 里的配置:
@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 中進(jìn)行過(guò)賦值操作Theme.of(context).headline).bodyText1
:讀取主題配置中鍵值為 bodyText1 的字體樣式
小技巧介紹
通常為了便于開發(fā)閱讀,我們也可以使用extension
對(duì) ThemeData 內(nèi)屬性進(jìn)行重命名獲取:
新建 extension_theme.dart,文件名字隨意:
///用于重命名顏色屬性 extension ThemeDataColorExtension on ThemeData { Color get bgColor => colorScheme.onBackground; ... } ///用于重命名字體樣式屬性 extension ThemeDataTextStyleExtension on ThemeData { TextStyle get bodyStyle => textTheme.bodyText1!; ... }
在UI頁(yè)面進(jìn)行引用導(dǎo)入使用,上面的 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 內(nèi)置字段不夠用,如何擴(kuò)展?
從 ThemeData 的構(gòu)造函數(shù)中我們可以看到,ThemeData 內(nèi)置的字段是有限的。假如我們的UI設(shè)計(jì)包含的色值數(shù)量或者字體樣式數(shù)量超出了 ThemeData 可供設(shè)置數(shù)量怎么辦呢?
比如:我們想新增一個(gè)色值配置,名字就叫 connerColor
,我們還想保持統(tǒng)一,一律通過(guò) ThemeData 來(lái)統(tǒng)一讀取統(tǒng)一配置,要如何處理呢?
小編在項(xiàng)目里是這么做的,將 ThemeData
進(jìn)行一層封裝,以新增 connerColor
為例,具體代碼結(jié)合下面????的一鍵換膚查詢。
如何實(shí)現(xiàn)一鍵換膚
有了ThemeData作為統(tǒng)一管理存放配置信息后,實(shí)現(xiàn)一鍵換膚的思路就很清晰了,大致是這樣的:
從上圖可以看到,除了需要ThemeData
用于存放配置信息,我們還需要封裝一個(gè)監(jiān)聽類用于監(jiān)聽選中主題發(fā)生變更,這個(gè)功能我們?cè)谙旅嬗?strong>provider來(lái)實(shí)現(xiàn)。
1. 首先在 yaml 新增引入 provider
dependencies: provider: ^6.0.2
2. 創(chuàng)建主題枚舉
假設(shè)我們提供兩種主題切換
///主題類型 enum ThemeEnum { yellow, red, }
3. ThemeData 進(jìn)行一層封裝處理
我們對(duì) ThemeData
進(jìn)行一層封裝處理,添加 connerColor
進(jìn)行顏色字段擴(kuò)展
///自定義模型,包裝一下 themeData class ThemeItem { final ThemeEnum themeEnum; final ThemeData themeData; // 擴(kuò)展一個(gè)字段,用于表示自定義色值 final Color connerColor; ThemeItem( this.themeEnum, this.themeData, { required this.connerColor, }); }
4. 創(chuàng)建一個(gè)主題管理類 ThemeConfig
abstract class ThemeConfig { ///記錄當(dāng)前選中主題 static late ThemeItem _currentTheme; static ThemeData get currentThemeData => _currentTheme.themeData; static ThemeEnum get currentTheme => _currentTheme.themeEnum; ///提供獲取擴(kuò)展的色值 static Color? get connerColor => _currentTheme.connerColor; ///設(shè)置選中主題,提供外部調(diào)用,更換當(dāng)前主題 static void initTheme(ThemeItem theme) { _currentTheme = theme; } }
5. 通過(guò)ThemeData進(jìn)行讀取保持統(tǒng)一
為保持統(tǒng)一通過(guò)ThemeData
進(jìn)行讀取,使用extension
對(duì)新增字段connerColor
進(jìn)行讀取擴(kuò)展
extension ExTheme on ThemeData { ///擴(kuò)展獲取自定義色值 Color get connerColor => ThemeConfig.connerColor!; }
6. 基于 provider 的使用
我們添加一個(gè)工具類,用于通知設(shè)置主題變更
class AppInfoProvider with ChangeNotifier { ThemeData get currentTheme => ThemeConfig.currentThemeData; ///切換主題 setTheme(ThemeItem theme) { ThemeConfig.initTheme(theme); notifyListeners(); } }
切換主題時(shí),直接調(diào)用:
Provider.of<AppInfoProvider>(context, listen: false).setTheme(themeItem);
7. 創(chuàng)建一個(gè)主題倉(cāng)庫(kù),里面存放兩套主題,用于演示 Demo
///主題倉(cāng)庫(kù) 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, ), ]; }
8.完整的 demo 代碼及效果
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('打開主題設(shè)置頁(yè)面'), ); } }
body_widget
class BodyWidget extends StatelessWidget { const BodyWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Container( height: 300, width: 300, //讀取標(biāo)準(zhǔn)色值 color: Theme.of(context).backgroundColor, child: Center( child: Container( height: 100, width: 150, //讀取自定義色值 color: Theme.of(context).connerColor, ), ), ); } }
兩個(gè)方塊,外層方塊讀取的 ThemeData 標(biāo)注字段色值,內(nèi)層方塊讀取擴(kuò)展字段色值。統(tǒng)一通過(guò) 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; } } }
///主題選擇頁(yè)面 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中如何使用和擴(kuò)展ThemeData實(shí)現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于flutter ThemeData擴(kuò)展的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Flutter學(xué)習(xí)筆記(三)RowColum布局
這篇文章主要介紹了Flutter學(xué)習(xí)筆記(三)RowColum布局,通俗來(lái)說(shuō),就是橫向布局和縱向布局的用法,需要的朋友可以參考下2023-04-04flutter中如何使用和擴(kuò)展ThemeData實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了flutter中如何使用和擴(kuò)展ThemeData實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Flutter SizedBox布局組件Widget使用示例詳解
這篇文章主要為大家介紹了Flutter SizedBox布局組件Widget使用示例詳解2023-02-02Flutter入門學(xué)習(xí)Dart語(yǔ)言變量及基本使用概念
這篇文章主要為大家介紹了Flutter入門學(xué)習(xí)Dart語(yǔ)言變量及基本使用概念,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Android開發(fā)中Dart語(yǔ)言7個(gè)很酷的特點(diǎn)
這篇文章主要為大家介紹了Android開發(fā)中Dart語(yǔ)言7個(gè)很酷的特點(diǎn)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05Flutter學(xué)習(xí)筆記(一)配置環(huán)境
這篇文章主要介紹了Flutter學(xué)習(xí)筆記(一)配置環(huán)境,Flutter?app使用了?Dart語(yǔ)言,源自于?Google,現(xiàn)在是?ECMA?的標(biāo)準(zhǔn),需要的朋友可以參考下2023-04-04一文詳解Dart如何實(shí)現(xiàn)多任務(wù)并行
這篇文章主要為大家介紹了Dart如何實(shí)現(xiàn)多任務(wù)并行示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03Dart多態(tài)控制反轉(zhuǎn)編碼規(guī)范實(shí)例詳解
這篇文章主要為大家介紹了Dart多態(tài)控制反轉(zhuǎn)編碼規(guī)范實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11谷歌Sky語(yǔ)言怎么樣?什么是Dart編程語(yǔ)言?
據(jù)外媒報(bào)道,在日前舉行Dart開發(fā)者峰會(huì)上,谷歌對(duì)外正式展示了Android最新的開發(fā)語(yǔ)言Sky,據(jù)悉,Sky本質(zhì)上就是谷歌自主的網(wǎng)頁(yè)開發(fā)語(yǔ)言Dart.2015-05-05