Flutter以两种方式实现App主题切换的代码

概述

App主题切换已经成为了一种流行的用户体验,丰富了应用整体UI视觉效果。例如,白天夜间模式切换。实现该功能的思想其实不难,就是将涉及主题的资源文件进行全局替换更新。说到这里,我想你肯定能联想到一种设计模式:观察者模式。多种观察对象(主题资源)来观察当前主题更新的行为(被观察对象),进行主题的更新。今天和大家分享在 Flutter 平台上如何实现主题更换。

效果

实现流程

在 Flutter 项目中,MaterialApp组件为开发者提供了设置主题的api:

 const MaterialApp({
 ...
 this.theme, // 主题
 ...
 })

通过 theme 属性,我们可以设置在MaterialApp下的主题样式。theme 是 ThemeData 的对象实例:

ThemeData({

 Brightness brightness,
 MaterialColor primarySwatch,
 Color primaryColor,
 Brightness primaryColorBrightness,
 Color primaryColorLight,
 Color primaryColorDark,

 ...

 })

ThemeData 中包含了很多主题设置,我们可以选择性的改变其中的颜色,字体等等。所以我们可以通过改变 primaryColor 来实现状态栏的颜色改变。并通过Theme来获取当前 primaryColor 颜色值,将其赋值到其他组件上即可。在触发主题更新行为时,通知 ThemeData 的 primaryColor改变行对应颜色值。 有了以上思路,接下来我们通过两种方式来展示如何实现主题的全局更新。

主题选项

在实例中我们以一下主题颜色为主:

/**
 * 主题选项
 */
import 'package:flutter/material.dart';

final List<Color> themeList = [
 Colors.black,
 Colors.red,
 Colors.teal,
 Colors.pink,
 Colors.amber,
 Colors.orange,
 Colors.green,
 Colors.blue,
 Colors.lightBlue,
 Colors.purple,
 Colors.deepPurple,
 Colors.indigo,
 Colors.cyan,
 Colors.brown,
 Colors.grey,
 Colors.blueGrey
];

EventBus 方式实现

Flutter中EventBus提供了事件总线的功能,以监听通知的方式进行主体间通信。我们可以在main.dart入口文件下注册主题修改的监听,通过EventBus发送通知来动态修改 theme。核心代码如下:

 @override
 void initState() {
 super.initState();
 Application.eventBus = new EventBus();
 themeColor = ThemeList[widget.themeIndex];
 this.registerThemeEvent();
 }

 /**
 * 注册主题切换监听
 */
 void registerThemeEvent() {
 Application.eventBus.on<ThemeChangeEvent>().listen((ThemeChangeEvent onData)=> this.changeTheme(onData));
 }

 /**
 * 刷新主题样式
 */
 void changeTheme(ThemeChangeEvent onData) {
 setState(() {
  themeColor = themeList[onData.themeIndex];
 });
 }

 @override
 Widget build(BuildContext context) {
 return MaterialApp(
  theme: ThemeData(
  primaryColor: themeColor
  ),
  home: HomePage(),
 );
 }

然后在更新主题行为的地方来发送通知刷新即可:

 changeTheme() async {
 Application.eventBus.fire(new ThemeChangeEvent(1));
 }

scoped_model 状态管理方式实现

了解 React、 React Naitve 开发的朋友对状态管理框架肯定都不陌生,例如 Redux 、Mobx、 Flux 等等。状态框架的实现可以帮助我们非常轻松的控制项目中的状态逻辑,使得代码逻辑清晰易维护。Flutter 借鉴了 React 的状态控制,同样产生了一些状态管理框架,例如 flutter_redux、scoped_model、bloc。接下来我们使用 scoped_model 的方式实现主题的切换。 关于 scoped_model 的使用方式可以参考pub仓库提供的文档:https://pub.dartlang.org/packages/scoped_model

1. 首先定义主题 Model

/**
 * 主题Model
 * Create by Songlcy
 */
import 'package:scoped_model/scoped_model.dart';

abstract class ThemeStateModel extends Model {

 int _themeIndex;
 get themeIndex => _themeIndex;

 void changeTheme(int themeIndex) async {
 _themeIndex = themeIndex;
 notifyListeners();
 }
}

在 ThemeStateModel 中,定义了对应的主题下标,changeTheme() 方法为更改主题,并调用 notifyListeners() 进行全局通知。

2. 注入Model

 @override
 Widget build(BuildContext context) {
 return ScopedModel<MainStateModel>(
  model: MainStateModel(),
  child: ScopedModelDescendant<MainStateModel>(
  builder: (context, child, model) {
   return MaterialApp(
   theme: ThemeData(
    primaryColor: themeList[model.themeIndex]
   ),
   home: HomePage(),
   );
  },
  )
 );
 }

3. 修改主题

 changeTheme(int index) async {
 int themeIndex = index;
 MainStateModel().of(context).changeTheme(themeIndex);
 }

可以看到,使用 scoped_model 的方式同样比较简单,思路和 EventBus 类似。以上代码我们实现了主题的切换,细心的朋友可以发现,我们还需要对主题进行保存,当下次启动 App 时,要显示上次切换的主题。Flutter中提供了 shared_preferences 来实现本地持久化存储。

主题持久化保存

当进行主题更换时,我们可以对主题进行持久化本地存储

 void changeTheme(int themeIndex) async {
 _themeIndex = themeIndex;
 SharedPreferences sp = await SharedPreferences.getInstance();
 sp.setInt("themeIndex", themeIndex);
 }

然后在项目启动时,取出本地存储的主题下标,设置在theme上即可

void main() async {
 int themeIndex = await getTheme();
 runApp(App(themeIndex));
}

Future<int> getTheme() async {
 SharedPreferences sp = await SharedPreferences.getInstance();
 int themeIndex = sp.getInt("themeIndex");
 if(themeIndex != null) {
 return themeIndex;
 }
 return 0;
}

@override
Widget build(BuildContext context) {
 return ScopedModel<MainStateModel>(
  model: mainStateModel,
  child: ScopedModelDescendant<MainStateModel>(
  builder: (context, child, model) {
   return MaterialApp(
   theme: ThemeData(
    primaryColor: themeList[model.themeIndex != null ? model.themeIndex : widget.themeIndex]
   ),
   home: HomePage(),
   );
  },
  )
 );
}

以上我们通过两种方式来实现了主题的切换,实现思想都是通过通知的方式来触发组件 build 进行刷新。那么两种方式有什么区别呢?

区别

从 print log 中,可以发现,当使用 eventbus 事件总线进行切换主题刷新时,_AppState 下的 build方法 和 home指向的组件界面  整体都会重新构建。而使用scoped_model等状态管理工具,_AppState 下的 build方法不会重新执行,只会刷新使用到了Model的组件,但是home对应的组件依然会重新执行build方法进行构建。所以我们可以得出以下结论:

两者方式都会导致 home 组件被重复 build。明显区别在于使用状态管理工具的方式可以避免父组件 build 重构。

源码已上传到 Github,详细代码可以查看

EventBus 实现整体代码:

import 'package:flutter/material.dart';
import 'package:event_bus/event_bus.dart';
import './config/application.dart';
import './pages/home_page.dart';
import './events/theme_event.dart';
import './constants/theme.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() async {
 int themeIndex = await getDefaultTheme();
 runApp(App(themeIndex));
}

Future<int> getDefaultTheme() async {
 // 从shared_preferences中获取上次切换的主题
 SharedPreferences sp = await SharedPreferences.getInstance();
 int themeIndex = sp.getInt("themeIndex");
 print(themeIndex);
 if(themeIndex != null) {
 return themeIndex;
 }
 return 0;
}

class App extends StatefulWidget {

 int themeIndex;
 App(this.themeIndex);

 @override
 State<StatefulWidget> createState() => AppState();
}

class AppState extends State<App> {

 Color themeColor;

 @override
 void initState() {
 super.initState();
 Application.eventBus = new EventBus();
 themeColor = ThemeList[widget.themeIndex];
 this.registerThemeEvent();
 }

 void registerThemeEvent() {
 Application.eventBus.on<ThemeChangeEvent>().listen((ThemeChangeEvent onData)=> this.changeTheme(onData));
 }

 void changeTheme(ThemeChangeEvent onData) {
 setState(() {
  themeColor = ThemeList[onData.themeIndex];
 });
 }

 @override
 Widget build(BuildContext context) {
 return MaterialApp(
  theme: ThemeData(
  primaryColor: themeColor
  ),
  home: HomePage(),
 );
 }

 @override
 void dispose() {
 super.dispose();
 Application.eventBus.destroy();
 }
}
 changeTheme() async {
 SharedPreferences sp = await SharedPreferences.getInstance();
 sp.setInt("themeIndex", 1);
 Application.eventBus.fire(new ThemeChangeEvent(1));
 }

scoped_model 实现整体代码:

import 'package:flutter/material.dart';
import 'package:event_bus/event_bus.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:shared_preferences/shared_preferences.dart';
import './config/application.dart';
import './pages/home_page.dart';
import './constants/theme.dart';
import './models/state_model/main_model.dart';
void main() async {
 int themeIndex = await getTheme();
 runApp(App(themeIndex));
}
Future<int> getTheme() async {
 SharedPreferences sp = await SharedPreferences.getInstance();
 int themeIndex = sp.getInt("themeIndex");
 if(themeIndex != null) {
 return themeIndex;
 }
 return 0;
}
class App extends StatefulWidget {
 final int themeIndex;
 App(this.themeIndex);
 @override
 _AppState createState() => _AppState();
}
class _AppState extends State<App> {
 @override
 void initState() {
 super.initState();
 Application.eventBus = new EventBus();
 }
 @override
 Widget build(BuildContext context) {
 return ScopedModel<MainStateModel>(
  model: MainStateModel(),
  child: ScopedModelDescendant<MainStateModel>(
  builder: (context, child, model) {
   return MaterialApp(
   theme: ThemeData(
    primaryColor: ThemeList[model.themeIndex != null ? model.themeIndex : widget.themeIndex]
   ),
   home: HomePage(),
   );
  },
  )
 );
 }
}
 changeTheme() async {
 int themeIndex = MainStateModel().of(context).themeIndex == 0 ? 1 : 0;
 SharedPreferences sp = await SharedPreferences.getInstance();
 sp.setInt("themeIndex", themeIndex);
 MainStateModel().of(context).changeTheme(themeIndex);
 }

总结

(0)

相关推荐

  • Flutter以两种方式实现App主题切换的代码

    概述 App主题切换已经成为了一种流行的用户体验,丰富了应用整体UI视觉效果.例如,白天夜间模式切换.实现该功能的思想其实不难,就是将涉及主题的资源文件进行全局替换更新.说到这里,我想你肯定能联想到一种设计模式:观察者模式.多种观察对象(主题资源)来观察当前主题更新的行为(被观察对象),进行主题的更新.今天和大家分享在 Flutter 平台上如何实现主题更换. 效果 实现流程 在 Flutter 项目中,MaterialApp组件为开发者提供了设置主题的api: const MaterialAp

  • 原生js更改css样式的两种方式

    下面我给大家介绍的是原生js更改CSS样式的两种方式: 1. 通过在javascript代码中的node.style.cssText="css表达式1:css表达式2:css表达式3  "的方式直接更改CSS样式. 2. 先在CSS样式表中对特定的类如"active类"设置样式(这里的active类是假定的,暂时不存在),然后再在javascript代码中通过node.classname="active"使得CSS样式表中对active类的样式设

  • mapper接口注入两种方式详解

    这篇文章主要介绍了mapper接口注入两种方式详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.使用模板方式: <!--使用模板类实现mybatis --> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg name="sqlSessionFacto

  • C#实体类转换的两种方式小结

    目录 C#实体类转换方式 以下提供两种方式 实现功能 开发环境 实现代码 C#实体类转为JSON字符串 总结 C#实体类转换方式 将一个实体类的数据赋值到另一个实体类中(亦或者实现深拷贝). 以下提供两种方式 一种是序列化 一种是泛型+反射 实现功能 两个实体类数据转换赋值 开发环境 开发工具: Visual Studio 2013 .NET Framework版本:4.5 实现代码 //学生类    private class Student {     public string name

  • Android Studio进行APP图标更改的两种方式总结

    百度了许多相关资料,对两种修改app图标的方式进行总结: 第一种:(最简单的方法) 将你准备好的 图标放入res目录下的drawable,在AndroidManifest.xml文件中,找到android:icon以及android:roundIcon这两个属性,设置为你放入的图标文件. 如图,appicon就是我准备替换的文件.注意保存时,保存名称不能有大写字母与空格,否则编译时会报错,此外,查到的资料中图片格式建议保存为.png.不过自己试验过.jpg与.png都是可以正确替换图标的. 在这

  • uniapp打包安卓App的两种方式(云打包、本地打包)方法详解

    在HBuilder上对APP提供了两种打包方式,云打包和本地打包,下面主要对这两种打包方式做个介绍 两者的区别:云打包相对简单,但是每天最多只能打包五次,而且在高峰期打包时间可能会很长,本地打包相对比较复杂,但是不限制次数,打包时间也短 一. uniapp云打包安卓App: 只需要设置相应参数即可.比较复杂的地方可能就是证书,如果你是测试包,Android的话直接选择共用证书即可,ios则需要申请相应证书,申请证书的具体方法官方也介绍的很清楚了,就不赘述了. 二. uniapp本地打包安卓App

  • Angular弹出模态框的两种方式

    在开始我们的blog之前,我们要先安装ngx-bootstrap-modal npm install ngx-bootstrap-modal --save 不然我们的模态框效果会难看到你想吐 一.弹出方式一(此方法来自https://github.com/cipchk/ngx-bootstrap-modal) 1.alert弹框 (1)demo目录 --------app.component.ts --------app.component.html --------app.module.ts

  • Angualrjs 表单验证的两种方式(失去焦点验证和点击提交验证)

    AngularJS提供了表单验证,但是验证的过程交互体验很不好,比如重设密码,重复密码的时候一键入就会提示密码不正确,现整理了两种方法,仅供借鉴. 一,点击提交验证 <form action="" class="form-horizontal col-md-9" name="reset_pwd" ng-submit="resetPwd()"> <div class="form-group"

  • jquery ajax提交表单数据的两种方式

    之前实现AJAX使用Javascript脚本一个一个敲出来的,很繁琐.学习Jquery之后就感觉实现AJAX并不是那么的困难了,当然除了Jquery框架外还有其它的优秀框架这里我就着重说下比较流行的Jquery.Jquery AJAX提交表单有两种方式,一是url参数提交数据,二是form提交(和平常一样在后台可以获取到Form表单的值).在所要提交的表单中,如果元素很多的话建议用第二种方式进行提交,当然你要是想练练"打字水平"的话用第一种方式提交也未尝不可,相信开发者都不想费白劲吧!

  • 详解使用Vue.Js结合Jquery Ajax加载数据的两种方式

    整理文档,搜刮出一个使用Vue.Js结合Jquery Ajax加载数据的两种方式的代码,稍微整理精简一下做下分享. 废话不多说,直接上代码 html代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>demo</title> <script src="js/jquery.js"

随机推荐