flutter fish-redux主题切换和全局store的使用

主题切换和全局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

上一篇:Linux 必备工具


下一篇:git将文件托管到github上遇到的问题