主题切换和全局store的使用:
这里在个人中心加了一个简单的切换夜间模式的功能,需要一个全局的Store这里直接是使用fish-redux的example中的global_store包里的东西,只是把themeColor换成自己定义的主题,至于初始化咱就在创建的地方初始化一下数据,如下代码:
class GlobalStore {
static Store<GlobalState> _globalStore;
static Store<GlobalState> get store {
return _globalStore ??= createStore<GlobalState>(
GlobalState()..appTheme = AppTheme.getInstance(false), buildReducer());
}
}
入口页也需要改造成fish-redux的方式,直接新建一个page就是,这个新建方式在之前的博客有提到,是他们团队提供的插件,这里就不在多做介绍了,如下代码:
Widget buildView(MainState state, Dispatch dispatch, ViewService viewService) {
return MaterialApp(
title: 'Flutter Demo',
theme: state.appTheme.dark ? darkTheme : lightTheme,
home: routes.buildPage('home', null),
routes: appRoutes,
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<Object>(builder: (BuildContext context) {
return routes.buildPage(settings.name, settings.arguments);
});
},
);
}
这里的darkTheme 和 lightTheme都在AppTheme中。
还需要在一个地方处理一下这个store,如下所示:
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'constant/theme.dart';
import 'global_store/state.dart';
import 'global_store/store.dart';
import 'home/index/page.dart';
import 'home/index/page/page.dart';
import 'home/mine/page.dart';
import 'home/page.dart';
import 'home/smart/page.dart';
import 'page.dart';
import 'radio/page.dart';
Map<String, WidgetBuilder> appRoutes = {
'/home': (BuildContext context) => routes.buildPage("home", null),
'/main': (BuildContext context) => routes.buildPage("main", null),
'/radioDetail': (BuildContext context) =>
routes.buildPage("radioDetail", null),
};
var routes = new PageRoutes(
pages: <String, Page<Object, dynamic>>{
'home': HomePage(),
'main': MainPage(),
'index': IndexPage(),
'smart': SmartPage(),
'mine': MinePage(),
'indexTab': TabPage(),
'radioDetail': RadioDetailPage(), //电台详情页
},
visitor: (String path, Page<Object, dynamic> page) {
/// XXX
if (page.isTypeof<GlobalBaseState>()) {
page.connectExtraStore<GlobalState>(GlobalStore.store,
(Object pageState, GlobalState appState) {
final GlobalBaseState p = pageState;
if (p.appTheme == appState.appTheme) {
return pageState;
} else {
if (pageState is Cloneable) {
final Object copy = pageState.clone();
final GlobalBaseState newState = copy;
newState.appTheme = appState.appTheme;
return newState;
}
}
});
}
page.enhancer.append(
viewMiddleware: <ViewMiddleware<dynamic>>[safetyView<dynamic>()],
adapterMiddleware: <AdapterMiddleware<dynamic>>[safetyAdapter<dynamic>()],
effectMiddleware: [_pageAnalyticsMiddleware()],
middleware: <Middleware<dynamic>>[logMiddleware<dynamic>()],
);
},
);
/// 简单的Effect AOP
/// 只针对页面的生命周期进行打印
EffectMiddleware<T> _pageAnalyticsMiddleware<T>({String tag = 'redux'}) {
return (AbstractLogic<dynamic> logic, Store<T> store) {
return (Effect<dynamic> effect) {
return (Action action, Context<dynamic> ctx) {
if (logic is Page<dynamic, dynamic> && action.type is Lifecycle) {
print('${logic.runtimeType} ${action.type.toString()} ');
}
return effect?.call(action, ctx);
};
};
};
}
Future appPushRoute(String path, BuildContext context,
{Map<String, dynamic> params}) async {
return await Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => routes.buildPage(path, params)));
}
Future appPushRemoveRoute(String path, BuildContext context,
{Map<String, dynamic> params}) async {
return await Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) => routes.buildPage(path, params)),
ModalRoute.withName(path));
}
Future appPushNameRoute(String path, BuildContext context) async {
return await Navigator.of(context).pushNamed(path);
}
这里其实也是在fish-redux上复制的,其实就是遍历所有page判断theme是否改变,改变了就重新clone赋值。
然后在你要用到的地方的State的实现GlobalBaseState,这个抽象类,如下所示:
abstract class GlobalBaseState {
AppTheme get appTheme;
set appTheme(AppTheme appTheme);
}
这个AppTheme类是我自己定义的,其实就是我们主题切换时对应的字体颜色等样式,这里就不贴出来了,下面有代码地址,大家可以自行clone跑一下。
这里列出来一个我实现这个GlobalBaseState的类,个人中心的State类,如下所示:
class MineState implements GlobalBaseState, Cloneable<MineState> {
bool darkOn;
bool gridOn;
@override
MineState clone() {
return MineState()
..appTheme = appTheme
..darkOn = darkOn
..gridOn = gridOn;
}
@override
AppTheme appTheme;
}
MineState initState(Map<String, dynamic> args) {
return MineState()
..gridOn = false
..darkOn = Constants.dark;
}
其实就是实现一下,然后在clone加上这个apptheme就行了,
调用切换主题如下代码:
Widget buildView(MineState state, Dispatch dispatch, ViewService viewService) {
return ListView(
children: <Widget>[
ListTile(
leading: Icon(Icons.brightness_2),
title: Text('夜间模式', style: state.appTheme.titleStyle),
trailing: Switch(
value: state.darkOn,
onChanged: (value) {
print("onchange---------$value");
GlobalStore.store
.dispatch(GlobalActionCreator.onchangeThemeColor(value));
dispatch(MineActionCreator.changeThemeAction(value));
dispatch(MineActionCreator.changeThemeEffect(value));
}),
),
ListTile(
leading: Icon(Icons.grid_on),
title: Text('GridView显示', style: state.appTheme.titleStyle),
trailing: Switch(
value: state.gridOn,
onChanged: (value) {
dispatch(MineActionCreator.changeGridAction(value));
}),
)
],
);
}
改变主题时候有个坑,不知道是不是我的方式不对还是咋的,理论上调用 GlobalStore.store .dispatch(GlobalActionCreator.onchangeThemeColor(value));就可以,但是我这里主题是切换了,但是我这列表每个Item文字颜色等都没改变,所以改成另外一种方式了。
这里我们可以看到我直接在这里还调用了其他方法,这里起作用的是这dispatch(MineActionCreator.changeThemeEffect(value));
我把这个action发送的 个人中心的effect里,如下所示:
Effect<MineState> buildEffect() {
return combineEffects(<Object, Effect<MineState>>{
MineAction.changeGridAction: _changeGridAction,
MineAction.changeThemeEffect: __changeThemeEffect,
});
}
void _changeGridAction(Action action, Context<MineState> ctx) {
SPUtils.save("gridOn", action.payload);
ctx.dispatch(MineActionCreator.changeGridValue(action.payload));
ctx.broadcast(MineActionCreator.changeGridValue(action.payload));
}
void __changeThemeEffect(Action action, Context<MineState> ctx) {
SPUtils.save("dark", action.payload);
ctx.broadcast(MineActionCreator.changeThemeAction(action.payload));
}
就是这个方法__changeThemeEffect接收到view中action然后我在这里发一个广播,我只要在item中注册这个广播就可以收到改变主题的action了,这里本来打算直接注册在reducer中的,但是发现不起作用所以最后注册到effect中才能接收到,如下代码:
Effect<ItemState> buildEffect() {
return combineEffects(<Object, Effect<ItemState>>{
ItemAction.onItemClick: _onItemClick,
MineAction.changeThemeAction: _changeThemeAction,
});
}
void _onItemClick(Action action, Context<ItemState> ctx) {
Channel channel = action.payload;
if (ctx.state.channel.channelid == channel.channelid) {
print(channel.ch_name);
appPushRoute('radioDetail', ctx.context,
params: {"title": channel.name, "name": channel.ch_name});
}
}
void _changeThemeAction(Action action, Context<ItemState> ctx) {
SPUtils.get("dark").then((res) {
print("get dark $res");
ctx.dispatch(TabActionCreator.changeThemeAction(res != null && res));
});
}
因此我这里还需要发一次action才能改变每个item的颜色,感觉比较麻烦,不知道大家还有没有更好的办法实现,如果有的话希望大神提供一下思路,小弟不胜感激。
流程差不多就这样了,大家可以直接clone代码,跑一下就可以看到效果了。后续还会介绍我在用flutter开发app过程种,Android和苹果遇到的问题,以及两个平台上插件的问题
传送门:demo