前言
做過UI開發(fā)的同學(xué)都知道崎苗,在開發(fā)中我們通常會將 文字大小些阅、色值 等內(nèi)容放在配置文件中伞剑,通過統(tǒng)一的管理類來讀取(嚴禁在UI代碼中寫死)市埋。以便后續(xù)調(diào)整時不用修改源碼黎泣,只需要修改配置文件即可。
例如這樣:
- 定義常量存放
/// 存放顏色常量
abstract class ColorConfigs {
static const Color background = Color(0xFFFF6600);
static const Color textHint = Color(0xFFA0A4A7);
}
- 通過統(tǒng)一獲取
/// GOOD
Container(
//通過 ColorConfig 獲取色值
color: ColorConfigs.background,
)
/// BAD
Container(
//寫死
color: Color(0xFFFF6600),
)
Flutter為我們提供了Theme類缤谎,可以讓我們節(jié)省封裝常量配置類(如上示例中的 ColorConfigs)的步驟抒倚。將色值、字體風格等配置內(nèi)容存入ThemeData中坷澡,子控件可統(tǒng)一通過 Theme.of(context)
讀取 color托呕、textStyle、等配置信息洋访。
本篇通過換膚demo镣陕,介紹在flutter項目中如何使用 theme 以及如何對 themeData 進行字段擴展,實現(xiàn)全局的主題配置管理姻政。
Theme 的基本使用方式
1. Theme 的注冊
MaterialApp(
theme: myThemeData, //一個ThemeData的實例呆抑,下面提供具體代碼
home: BodyWidget(),
)
我們做全局的主體配置,在 MaterialApp 中對 theme 字段進行入?yún)①x值汁展。示例代碼中的 myThemeData 是一個 ThemeData 的實現(xiàn)實例鹊碍,可通過 ThemeData 的構(gòu)造方法來查看其可供保存的主體及樣式信息,按照各自所需進行參數(shù)賦值食绿。
下面是小編在自己項目中用到的ThemeData配置項侈咕,定義了各種狀態(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 中進行過賦值操作 -
Theme.of(context).headline).bodyText1
:讀取主題配置中鍵值為 bodyText1 的字體樣式
小技巧介紹
通常為了便于開發(fā)閱讀囤攀,我們也可以使用
extension
對 ThemeData 內(nèi)屬性進行重命名獲瘸胃伞:
新建 extension_theme.dart,文件名字隨意:
///用于重命名顏色屬性
extension ThemeDataColorExtension on ThemeData {
Color get bgColor => colorScheme.onBackground;
...
}
///用于重命名字體樣式屬性
extension ThemeDataTextStyleExtension on ThemeData {
TextStyle get bodyStyle => textTheme.bodyText1!;
...
}
在UI頁面進行引用導(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)置字段不夠用掌腰,如何擴展狰住?
從 ThemeData 的構(gòu)造函數(shù)中我們可以看到,ThemeData 內(nèi)置的字段是有限的齿梁。假如我們的UI設(shè)計包含的色值數(shù)量或者字體樣式數(shù)量超出了 ThemeData 可供設(shè)置數(shù)量怎么辦呢催植?
比如:我們想新增一個色值配置肮蛹,名字就叫 connerColor
,我們還想保持統(tǒng)一创南,一律通過 ThemeData 來統(tǒng)一讀取統(tǒng)一配置伦忠,要如何處理呢?
小編在項目里是這么做的扰藕,將 ThemeData
進行一層封裝缓苛,以新增 connerColor
為例,具體代碼請看????一鍵換膚代碼介紹邓深。
如何實現(xiàn)一鍵換膚
有了ThemeData作為統(tǒng)一管理存放配置信息后,實現(xiàn)一鍵換膚的思路就很清晰了笔刹,大致是這樣的:
從上圖可以看到芥备,除了需要ThemeData
用于存放配置信息,我們還需要封裝一個監(jiān)聽類用于監(jiān)聽選中主題發(fā)生變更舌菜,這個功能我們在下面用provider來實現(xiàn)萌壳。
1. 首先在 yaml 新增引入 provider
dependencies:
provider: ^6.0.2
2. 創(chuàng)建主題枚舉,假設(shè)我們提供兩種主題切換
///主題類型
enum ThemeEnum {
yellow,
red,
}
3. 我們對 ThemeData
進行一層封裝處理日月,添加 connerColor
進行顏色字段擴展
///自定義模型袱瓮,包裝一下 themeData
class ThemeItem {
final ThemeEnum themeEnum;
final ThemeData themeData;
// 擴展一個字段,用于表示自定義色值
final Color connerColor;
ThemeItem(
this.themeEnum,
this.themeData, {
required this.connerColor,
});
}
4. 創(chuàng)建一個主題管理類 ThemeConfig
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;
///設(shè)置選中主題爱咬,提供外部調(diào)用尺借,更換當前主題
static void initTheme(ThemeItem theme) {
_currentTheme = theme;
}
}
5. 為保持統(tǒng)一通過ThemeData
進行讀取,使用extension
對新增字段connerColor
進行讀取擴展
extension ExTheme on ThemeData {
///擴展獲取自定義色值
Color get connerColor => ThemeConfig.connerColor!;
}
6. 基于 provider 的使用精拟,我們添加一個工具類燎斩,用于通知設(shè)置主題變更
class AppInfoProvider with ChangeNotifier {
ThemeData get currentTheme => ThemeConfig.currentThemeData;
///切換主題
setTheme(ThemeItem theme) {
ThemeConfig.initTheme(theme);
notifyListeners();
}
}
切換主題時,直接調(diào)用:
Provider.of<AppInfoProvider>(context, listen: false).setTheme(themeItem);
7. 創(chuàng)建一個主題倉庫蜂绎,里面存放兩套主題栅表,用于演示 Demo
///主題倉庫
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è)置頁面'),
);
}
}
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 標注字段色值,內(nèi)層方塊讀取擴展字段色值践美。統(tǒng)一通過 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,
),
);
}
}