专栏首页01二进制Flutter主题切换——让你的APP也能一键换肤

Flutter主题切换——让你的APP也能一键换肤

为了让你的 App 更美观,主题切换已经是一个必不可少的功能了,但如果想在传统的 Android 和 iOS 上分别适配不同的主题相当繁琐。但这一切,在 Flutter 中都非常容易实现。今天我们就来看看,如何在 Flutter 中给你的 App 添加换肤功能。

添加依赖

在该案例中,我使用到了 providerflustars 两个库,简单介绍一下这两个库:

provider

官方推荐的状态管理库,相比其他状态管理库使用起来比较方便。

状态管理:通俗的讲,当我们想在多个页面(组件/Widget)之间共享状态(数据),或者一个页面(组件/Widget)中的多个子组件之间共享状态(数据),这个时候我们就可以用 Flutter 中的状态管理来管理统一的状态(数据),实现不同组件直接的传值和数据共享。

flustars

号称“Flutter 全网最全常用工具类”,其中包括了SpUtilScreenUtilTimelineUtil等常见工具类,这里我们要使用的是SpUtil这个部分,用于存储用户所选择的主题信息。


以上就是关于我们使用的两个第三方库的介绍,如果想要使用,我们需要在pubspec.yaml文件中添加如下内容:

provider: ^4.0.5flustars: ^0.2.6+1

准备工作做好了,接下来我们就开始编码吧。

添加主题样式

我们需要先想好自己所需要切换的主题样式列表,如果觉得麻烦的可以直接用下面的内容:

Map<String, Color> themeColorMap = {  'gray': Colors.grey,  'blue': Colors.blue,  'blueAccent': Colors.blueAccent,  'cyan': Colors.cyan,  'deepPurple': Colors.purple,  'deepPurpleAccent': Colors.deepPurpleAccent,  'deepOrange': Colors.orange,  'green': Colors.green,  'indigo': Colors.indigo,  'indigoAccent': Colors.indigoAccent,  'orange': Colors.orange,  'purple': Colors.purple,  'pink': Colors.pink,  'red': Colors.red,  'teal': Colors.teal,  'black': Colors.black,};

使用 Provider 进行全局状态管理

然后我们就需要使用 Provider 来进行全局的状态管理了。首先先创建一个app_provider.dart文件,然后添加如下代码:

class AppInfoProvider with ChangeNotifier {  String _themeColor = '';
  String get themeColor => _themeColor;
  setTheme(String themeColor) {    _themeColor = themeColor;    notifyListeners();  }}

因为是全局的状态管理,接下来我们需要在main.dart文件中配置一下刚才创建的 provider,有多个状态管理就使用 MultiProvider,单个的使用 Provider.value 就行了。(考虑到未来项目的扩展,这里我就直接使用 MultiProvider)了

class MyApp extends StatelessWidget {  Color _themeColor;
  @override  Widget build(BuildContext context) {    return MultiProvider(      providers: [ChangeNotifierProvider.value(value: AppInfoProvider())],      child: Consumer<AppInfoProvider>(        builder: (context, appInfo, _) {          String colorKey = appInfo.themeColor;          if (themeColorMap[colorKey] != null) {            _themeColor = themeColorMap[colorKey];          }
          return MaterialApp(            title: 'Flutter Demo',            theme: ThemeData(              primaryColor: _themeColor,              floatingActionButtonTheme:                  FloatingActionButtonThemeData(backgroundColor: _themeColor),            ),            home: MyHomePage(title: 'Flutter Theme Change demo'),          );        },      ),    );  }}

如果想要在某个地方改变主题,我们只需要执行下面这行代码即可。

Provider.of<AppInfoProvider>(context).setTheme(colorKey);

我们先来说说上面这段代码,重点就在于 ThemeData 的设置:

我们看看ThemeData部分数据定义:

ThemeData({  Brightness brightness, //深色还是浅色  MaterialColor primarySwatch, //主题颜色样本,见下面介绍  Color primaryColor, //主色,决定导航栏颜色  Color accentColor, //次级色,决定大多数Widget的颜色,如进度条、开关等。  Color cardColor, //卡片颜色  Color dividerColor, //分割线颜色  ButtonThemeData buttonTheme, //按钮主题  Color cursorColor, //输入框光标颜色  Color dialogBackgroundColor,//对话框背景颜色  String fontFamily, //文字字体  TextTheme textTheme,// 字体主题,包括标题、body等文字样式  IconThemeData iconTheme, // Icon的默认样式  TargetPlatform platform, //指定平台,应用特定平台控件风格  ...})

上面只是ThemeData的一小部分属性,完整的数据定义读者可以查看 SDK。

之所以使用floatingActionButtonTheme单独设置floatingActionButton而不是使用accentTextTheme,是因为会有警告 ⚠️The support for configuring the foreground color of FloatingActionButtons using ThemeData.accentIconTheme has been deprecated. Please use ThemeData.floatingActionButtonTheme instead. See https://flutter.dev/go/remove-fab-accent-theme-dependency. This feature was deprecated after v1.13.2.意思就是这个属性将会在1.13.2中被废弃。不过并不影响我们现在的使用。

更多关于主题的内容可以参考 ?颜色和主题[1]

持久化选择的主题

这里就需要使用到一开始提到的flustars中的SpUtil了,我们一般会在页面初始化加载的时候读取保存的颜色信息,所以我们需要在初始化页面配置如下代码:

String _colorKey;
@overridevoid initState() {  super.initState();  _initAsync();}
Future<void> _initAsync() async {  await SpUtil.getInstance();  _colorKey = SpUtil.getString('key_theme_color', defValue: 'blue');  // 设置初始化主题颜色  Provider.of<AppInfoProvider>(context, listen: false).setTheme(_colorKey);}

await SpUtil.getInstance();这段代码用于加载SpUtil库,通过看源码我们知道,这个库采用了单例模式,当然,这不是这篇文章的重点。

上面这段代码用于初始化主题,我们通过SpUtil.getString('key_theme_color', defValue: 'blue');获取保存的主题信息,然后再使用Provider.of<AppInfoProvider>(context, listen: false).setTheme(_colorKey);设置主题即可。

初始化主题弄好了,那选择的代码又如何编写呢?

很简单,只需要才合适的地方调用下面的代码就可以了。

setState(() {  _colorKey = key;});SpHelper.putString('key_theme_color', key);Provider.of<AppInfoProvider>(context).setTheme(key);

思路和上面大同小异,无非是将getString换成了putString

切换主题控件的编写

上面的代码提供了切换主题的思路,但是对于用户来说,他们所要做的是有一个界面可以让他们直接切换主题,因此,下面我们来编写切换主题的控件。

因为切换主题通常会在设置界面中出现,所以这里我用了一个ExpansionTile,这是一个可以展开的ListTile,代码如下:

…………ExpansionTile(  leading: Icon(Icons.color_lens),  title: Text('颜色主题'),  initiallyExpanded: false,  children: <Widget>[    Padding(      padding: EdgeInsets.only(left: 10, right: 10, bottom: 10),      child: Wrap(        spacing: 8,        runSpacing: 8,        children: themeColorMap.keys.map((key) {          Color value = themeColorMap[key];          return InkWell(            onTap: () {              setState(() {                _colorKey = key;              });              SpUtil.putString('key_theme_color', key);              Provider.of<AppInfoProvider>(context, listen: false)                  .setTheme(key);            },            child: Container(              width: 40,              height: 40,              color: value,              child: _colorKey == key                  ? Icon(                      Icons.done,                      color: Colors.white,                    )                  : null,            ),          );        }).toList(),      ),    )  ],),…………

效果如下:

上面这段代码就是将我们最开始选定的一些主题themeColorMap展示出来,告诉用户可以切换哪些主题。其中onTap内的代码就是上一节中提到的设置颜色主题的方法,InkWell主要用于提供主题色的点击效果,换成GestureDetector也是可以的。

至此我们的换肤功能也就完成了,想要获取完整代码的可以关注公众号「01 二进制」,后台回复「Flutter 主题切换」。

最后

以上就是关于如何在 Flutter 中切换主题的详细内容了。可以看出,相较于原生应用主题的适配,在 Flutter 中实现换肤的功能简单很多了。

最后来发布一篇预告,因为 iOS 13 和 Android 10 系统上都新增了「深色模式」,在文中我也提到了ThemeDataBrightness brightness属性用于表示深色还是浅色。下一篇文章我就来聊一聊深色模式的适配。

References

[1] 颜色和主题: https://book.flutterchina.club/chapter7/theme.html

本文分享自微信公众号 - 01二进制(gh_d1999add1857),作者:雇个城管打天下

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 聊一聊2038年问题

    庚子年是中国传统的 60 甲子纪年法。擅长观测的古人很早就发现,每当年份执行到庚子这一年,自然灾害变多,突发事件频频,一些震动世界、影响安定的大事件也容易发生在...

    出其东门
  • Leetcode题解——849/950

    我们将有人的座位的下标记录到一个list中,剩下的事情就是要找一个点,距离list中某个元素的最大距离

    出其东门
  • 快速适配 Flutter 之语言国际化

    如果你希望你的APP走出海外,那么就需要你在编写代码时考虑支持不同的语言环境,设置一些“本地化”的值,例如文本/布局。Flutter本身是具备国际化的,在适配方...

    出其东门
  • 【Python 第21课】 函数的参数

    今天发现了一个iPad上的游戏,叫Cargo-Bot。这个游戏需要你用指令控制一个机械臂去搬箱子。游戏里蕴含了很多编程的思想,包括循环、函数调用、条件判断、寄...

    Crossin先生
  • 100天搞定机器学习|day38 反向传播算法推导

    上集我们学习了反向传播算法的原理,今天我们深入讲解其中的微积分理论,展示在机器学习中,怎么理解链式法则。

    统计学家
  • 过程(五)可选参数和可变参数

    大家好,上节介绍了过程传递参数时,形参与实参结合的两种方式,传地址和传值。本节将介绍可选参数和可变参数。

    无言之月
  • 三分钟让 IntelliJ IDEA 显示简体中文

    JetBrains 作为一间跨国公司,我们非常重视每一位用户使用的语言。我们知道若用户能用自己习惯且熟悉的语言,将可以更好地理解我们的服务及产品功能。因此从去年...

    bennyhuo
  • python基础—函数参数

    注意:  最后一个参数的顺序是错误的,因为可变的位置参数,是不能放在关键字参数后面的,否则会出错。

    dogfei
  • 谈一谈|如何利用函数的各种参数

    Python的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参...

    算法与编程之美
  • JS实战开发经验!函数多参数传参技巧

    HTML5学堂-码匠:掌握JavaScript代码的你,一定编写封装过函数,为了提升函数的控制性,必不可少的就是参数,必选可选的一大堆参数罗列出来,函数调用貌似...

    HTML5学堂

扫码关注云+社区

领取腾讯云代金券