欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-06-21 08:26:42
概述app主题切换已经成为了一种流行的用户体验,丰富了应用整体ui视觉效果。例如,白天夜间模式切换。实现该功能的思想其实不难,就是将涉及主题的资源文件进行全局替换更新。说到这里,我想你肯定能联想到一种...

概述

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

效果

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

实现流程

在 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仓库提供的文档:

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);
 }

总结