Flutter 一键换肤、切换主题

App项目一键换肤功能比较常见了,一般项目都附带有该功能,由于近期项目内也加入了此功能,也顺带记录下过程。

由于产品说还想要从后台配置相关配色,通过后台随时控制,所以我反手就给他一个大比兜,然后就开写代码了~ (づ ̄3 ̄)づ╭❤~

首先第一步先配置下主题相关:

创了一个类专门管理以及处理颜色读取相关:

abstract class ThemeColorConfig {
  //正常模式、也可读取后台数据模式
  Map<String, dynamic> normalColorInfo = {};

  //暗黑模式、可跟随系统
  Map<String, dynamic> darkColorInfo = {};

  Color configColor(String colorKey) {
    //读取是否是暗黑模式 这里是读取存储的模式,使用的SpUtil封装的,比较简易,就不贴了
    String isDark = CommonSpUtil.getThemeType();
    Map colorInfo = isDark == "isDark" ? darkColorInfo : normalColorInfo;
    return ColorsUtil.hexToColor(colorInfo[colorKey]);
  }
}

class ThemColorUtil extends ThemeColorConfig {
  @override
  // TODO: implement darkColorInfo
  // 暗黑模式,可随系统变化
  Map<String, dynamic> get darkColorInfo => {
        'backgroundColor': "#2865ff",
        'text': "#6A5ACD",
        'button': "#1E90FF",
        'content': "#000000",
        'themColor': "#8A2BE2",
        'line': "#999999",
        'space': "#E8E8E8",
        'cloc3c3c3': "#c3c3c3",
        'redText': "#FF6650",
        'cloC6C9DB': "#C6C9DB",
      };

  @override
  // TODO: implement normalColorInfo
  // 正常模式、也可读取后台数据模式
  Map<String, dynamic> get normalColorInfo =>
      SpUtil.getString("colorMaps")!.isNotEmpty
          ? jsonDecode(SpUtil.getString("colorMaps")!)
          : {
              'text': "#66FFCC",
              'button': "#1F1D2B",
              'backgroundColor': "#1F1D2B",
              'content': "#000000",
              'themColor': "#1F1D2B",
              'line': "#999999",
              'space': "#E8E8E8",
              'c3c3c3': "#c3c3c3",
              'redText': "#FF6650"
            };
}

创建一个ColorsUtil类,以及使用上面类来管理颜色以及主要主题色,便于使用和取值:

class ColorsUtil {
  // 颜色键值,取值用
  static String them = "themColor";
  static String background = "backgroundColor";
  static String text = "text";
  static String button = "button";
  static String content = "content";
  static String line = "line";
  static String space = "space";
  static String cloc3c3c3 = "cloc3c3c3";
  static String redText = "redText";
  static String cloC6C9DB = "cloC6C9DB";

  /// 十六进制颜色,
  /// hex, 十六进制值,例如:0xffffff,
  /// alpha, 透明度 [0.0,1.0]
  static Color hexToColor(dynamic string) {
    /// 如果传入的十六进制颜色值不符合要求,返回默认值
    if (string == null ||
        string.length != 7 ||
        int.tryParse(string.substring(1, 7), radix: 16) == null) {
      string = '#999999';
    }

    return Color(int.parse(string.substring(1, 7), radix: 16) + 0xFF000000);
  }

  ///生成随机颜色
  static Color randomColor() {
    return Color.fromRGBO(
        Random().nextInt(255), Random().nextInt(255), Random().nextInt(255), 1);
  }

  /// 项目主题色
  Color themeColor = ThemColorUtil().configColor(
      them); //Color(0xFF1F1D2B); //ColorsUtil.hexToColor("#1F1D2B");

  /// 项目背景色
  Color backgroundColor = ThemColorUtil().configColor(background);

  /// 文本
  Color textColor = ThemColorUtil().configColor(text);

  /// cloC6C9DB
  Color colorC6C9DB = ThemColorUtil().configColor(cloC6C9DB);
}

好了,主题色值基本配置完毕,可以本地设置好多种肤色或者通过后台接口请求来,然后根据键值对进行对比取值即可。

第二步:

切换主题:

class ThemeTool {
  /// 切换主题
  static changeTheme() {
    ThemeMode mode = getLocalThemeModel();
    ThemeData themeData = getLocalThemeData();
    EasyLoadingStyle easyLoadingStyle = EasyLoadingStyle.dark;
    if (mode == ThemeMode.dark) {
      easyLoadingStyle = EasyLoadingStyle.light;
    } else if (mode == ThemeMode.system) {
      if (!Get.isDarkMode) {
        easyLoadingStyle = EasyLoadingStyle.light;
      }
    }
    EasyLoading.instance.loadingStyle = easyLoadingStyle;
    Get.changeThemeMode(mode);
    Get.changeTheme(themeData);

    //这里设置这个延迟原因是:在调用切换主题后,无法立即生效,会有一些延迟,如果不延迟会读取还是上个主题
    //使用Get 强制更新app状态
    Future.delayed(const Duration(milliseconds: 300), () {
      print("执行这里");
      Get.forceAppUpdate();
    });
  }

  /// 获取本地 主题配置
  static getLocalThemeModel() {
    //读取是否是暗黑模式
    String isDark = CommonSpUtil.getThemeType();
    ThemeMode themeMode = ThemeMode.light;
    if (isDark == "isDark") {
      themeMode = ThemeMode.system;
    } else {
      themeMode = ThemeMode.light;
    }
    return themeMode;
  }

  static getLocalThemeData() {
    //读取是否是暗黑模式
    String isDark = CommonSpUtil.getThemeType();
    ThemeData themeData = ThemeData.light();
    if (isDark == "isDark") {
      if (!Get.isDarkMode) {
        themeData = ThemeData(brightness: Brightness.dark);
      } else {
        themeData = ThemeData(brightness: Brightness.light);
      }
    } else {
      themeData = ThemeData(brightness: Brightness.light);
    }
    return themeData;
  }
}

最后,配置main入口函数中的GetMaterialApp

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      designSize: const Size(375, 812),
      builder: (context, w) {
        return GetMaterialApp(
          title: 'App',
          debugShowCheckedModeBanner: false,
          initialBinding: InitBinding(),
          initialRoute: RouterUtil.tabBar,
          getPages: RouterUtil.getPages,
          // translations: StringRes(),
          defaultTransition: Transition.cupertino,
          locale: LocaleTool.languageConfig().isNotEmpty
              ? Locale(LocaleTool.languageConfig()[0],
                  LocaleTool.languageConfig()[1])
              : null, //默认展示本地语言
          fallbackLocale: const Locale('zh', 'CN'), //语言选择无效时,备用语言
          /// 支持语言
          supportedLocales: S.delegate.supportedLocales,
          localizationsDelegates: [
            GlobalMaterialLocalizations.delegate,
            GlobalWidgetsLocalizations.delegate,
            GlobalCupertinoLocalizations.delegate,
            CustomLocalDelegate.delegate,
            S.delegate
          ],
          theme: ThemeData(brightness: Brightness.light),
          darkTheme: ThemeData(brightness: Brightness.dark),

          **/// 配置 本地存储 主题类型**
          themeMode: ThemeTool.getLocalThemeModel(),
          builder: EasyLoading.init(builder: (context, child) {
            return GestureDetector(
              onTap: () {
                FocusScopeNode currentFocus = FocusScope.of(context);
                if (!currentFocus.hasPrimaryFocus &&
                    currentFocus.focusedChild != null) {
                  FocusManager.instance.primaryFocus?.unfocus();
                }
              },
              child: child,
            );
          }),
        );
      },
    );
  }
}

到这一步基本一键切换主题,就基本完成了,可以尽情切换了。(当时我也以为完事了,但是忽略了一个问题,跟随系统可变)

上面配置完以后,通过测试发现,无法跟随系统变那,不符合需求,那么就需要监听手机的主题切换了。

我们可以在项目首页home_page内,继承WidgetsBindingObserver来监听

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

重写:didChangePlatformBrightness方法,在此方法内切换主题即可

  @override
  void didChangePlatformBrightness() {
    // 系统自动变化、切换暗黑模式和正常模式,回调方法
    // TODO: implement didChangePlatformBrightness
    super.didChangePlatformBrightness();
    ThemeTool.changeTheme(); //监测 自动切换暗黑和正常模式
  }

效果如下:

Simulator Screen Shot - iPhone 14 Pro Max - 2023-01-17 at 17.40.34.png

QQ20230118-111706.gif

我这目前只演示了替换导航颜色,其他文本以及颜色目前未全部处理,不过千篇一律,按照色值读取以及设置即可~

长风破浪会有时,直挂云帆济沧海!加油~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容