在 flutter 中如何使用和擴展 `ThemeData`

前言

做過UI開發(fā)的同學(xué)都知道崎苗,在開發(fā)中我們通常會將 文字大小些阅、色值 等內(nèi)容放在配置文件中伞剑,通過統(tǒng)一的管理類來讀取(嚴禁在UI代碼中寫死)市埋。以便后續(xù)調(diào)整時不用修改源碼黎泣,只需要修改配置文件即可。

例如這樣:

  1. 定義常量存放
/// 存放顏色常量
abstract class ColorConfigs {
  static const Color background = Color(0xFFFF6600);
  static const Color textHint = Color(0xFFA0A4A7);
}
  1. 通過統(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,
      ),
    );
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拨脉,隨后出現(xiàn)的幾起案子哆姻,更是在濱河造成了極大的恐慌,老刑警劉巖玫膀,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矛缨,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機箕昭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門灵妨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人落竹,你說我怎么就攤上這事泌霍。” “怎么了述召?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵朱转,是天一觀的道長。 經(jīng)常有香客問我积暖,道長藤为,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任夺刑,我火速辦了婚禮缅疟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遍愿。我一直安慰自己存淫,他們只是感情好,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布沼填。 她就那樣靜靜地躺著桅咆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪倾哺。 梳的紋絲不亂的頭發(fā)上轧邪,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機與錄音羞海,去河邊找鬼忌愚。 笑死,一個胖子當著我的面吹牛却邓,可吹牛的內(nèi)容都是我干的硕糊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼腊徙,長吁一口氣:“原來是場噩夢啊……” “哼简十!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起撬腾,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤螟蝙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后民傻,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胰默,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡场斑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了牵署。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漏隐。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖奴迅,靈堂內(nèi)的尸體忽然破棺而出青责,到底是詐尸還是另有隱情,我是刑警寧澤取具,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布脖隶,位于F島的核電站,受9級特大地震影響者填,放射性物質(zhì)發(fā)生泄漏浩村。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一占哟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酿矢,春花似錦榨乎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至策肝,卻和暖如春肛捍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背之众。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工拙毫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人棺禾。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓缀蹄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親膘婶。 傳聞我的和親對象是個殘疾皇子缺前,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容