redux次要由Store、Action、Reducer三局部组成

  • Store用于存储和治理State
  • Action用于用户触发的一种行为
  • Reducer用于依据Action产生新的State

    flutter redux流程

    1.Widget通过StoreConnector绑定Store中的State数据
    2.Widget通过Action触发一种新的行为
    3.Reducer依据收到的Action更新State
    4.更新Store中的State绑定的Widget
    依据以上流程,咱们实现我的项目中的主题切换性能。

    我的项目集成

    flutter redux库

    集成flutter redux

    批改我的项目根目录下pubspec.yaml,并增加依赖

    flutter_redux: ^0.5.3

    初始化Store

    首先看下Store的构造函数,如上面代码所示

     Store(  this.reducer, {  State initialState,  List<Middleware<State>> middleware = const [],  bool syncStream: false,  bool distinct: false,})  : _changeController = new StreamController.broadcast(sync: syncStream) {  _state = initialState;  _dispatchers = _createDispatchers(    middleware,    _createReduceAndNotify(distinct),  );}

    依据下面的构造函数,咱们首先须要创立State,并且还须要实现State初始化;而后须要创立Reducer;最初须要创立Middleware(暂不是本文须要解说的内容);

创立State

创立一个State对象AppState,用于贮存须要共享的主题数据,并且实现AppState初始化工作,如上面代码所示

class AppState {  ThemeData themeData;  AppState({this.themeData});  factory AppState.initial() => AppState(themeData: AppTheme.theme);}

AppTheme类中定义了一个默认主题theme,如上面代码所示

class AppTheme {  static final ThemeData _themeData = new ThemeData.light();  static get theme {    return _themeData.copyWith(      primaryColor: Colors.black,    );  }}

到此,实现了State的相干操作。

创立Reducer

创立一个Reducer办法appReducer,为AppState类里的每一个参数创立一个Reducer,如上面代码所示

AppState appReducer(AppState state, action) {  return AppState(    themeData: themeReducer(state.themeData, action),  );}

而themeReducer将ThemeData和所有跟切换主题的行为绑定在一起,如上面代码所示

final themeReducer = combineReducers<ThemeData>([  TypedReducer<ThemeData, RefreshThemeDataAction>(_refresh),]);ThemeData _refresh(ThemeData themeData, action) {  themeData = action.themeData;  return themeData;}

通过flutter redux的combineReducers与TypedReducer将RefreshThemeDataAction和_refresh绑定在一起,当用户每次收回RefreshThemeDataAction时,都会触发_refresh,用来更新themeData。

创立Action

创立一个Action对象RefreshThemeDataAction,如上面代码所示

class RefreshThemeDataAction{  final ThemeData themeData;  RefreshThemeDataAction(this.themeData);}

RefreshThemeDataAction的参数themeData是用来接管新切换的主题。

代码集成

创立Store所有的筹备工作都已筹备,上面创立Store,如上面代码所示

final store = new Store<AppState>(    appReducer,    initialState: AppState.initial(), );

而后用StoreProvider加载store,MaterialApp通过StoreConnector与Store放弃连贯。到此咱们曾经实现了flutter redux的初始化工作,如上面代码所示

void main() {  final store = new Store<AppState>(    appReducer,    initialState: AppState.initial(),  );  runApp(OpenGitApp(store));}class OpenGitApp extends StatelessWidget {  final Store<AppState> store;  OpenGitApp(this.store);  @override  Widget build(BuildContext context) {    return new StoreProvider<AppState>(      store: store,      child: StoreConnector<AppState, _ViewModel>(        converter: _ViewModel.fromStore,        builder: (context, vm) {          return new MaterialApp(            theme: vm.themeData,            routes: AppRoutes.getRoutes(),          );        },      ),    );  }}

StoreConnector通过converter在_ViewModel中转化store.state的数据,最初通过builder返回理论须要更新主题的控件,这样就实现了数据和控件的绑定。_ViewModel的代码如上面所示

class _ViewModel {  final ThemeData themeData;  _ViewModel({this.themeData});  static _ViewModel fromStore(Store<AppState> store) {    return _ViewModel(      themeData: store.state.themeData,    );  }}

用户行为

最初,只须要增加切换主题局部的代码即可,这部分代码是从官网gallery demo里的Style/Colors copy进去的,不做过多剖析,如上面代码所示

const double kColorItemHeight = 48.0;class Palette {  Palette({this.name, this.primary, this.accent, this.threshold = 900});  final String name;  final MaterialColor primary;  final MaterialAccentColor accent;  final int      threshold; // titles for indices > threshold are white, otherwise black  bool get isValid => name != null && primary != null && threshold != null;}final List<Palette> allPalettes = <Palette>[  new Palette(      name: 'RED',      primary: Colors.red,      accent: Colors.redAccent,      threshold: 300),  new Palette(      name: 'PINK',      primary: Colors.pink,      accent: Colors.pinkAccent,      threshold: 200),  new Palette(      name: 'PURPLE',      primary: Colors.purple,      accent: Colors.purpleAccent,      threshold: 200),  new Palette(      name: 'DEEP PURPLE',      primary: Colors.deepPurple,      accent: Colors.deepPurpleAccent,      threshold: 200),  new Palette(      name: 'INDIGO',      primary: Colors.indigo,      accent: Colors.indigoAccent,      threshold: 200),  new Palette(      name: 'BLUE',      primary: Colors.blue,      accent: Colors.blueAccent,      threshold: 400),  new Palette(      name: 'LIGHT BLUE',      primary: Colors.lightBlue,      accent: Colors.lightBlueAccent,      threshold: 500),  new Palette(      name: 'CYAN',      primary: Colors.cyan,      accent: Colors.cyanAccent,      threshold: 600),  new Palette(      name: 'TEAL',      primary: Colors.teal,      accent: Colors.tealAccent,      threshold: 400),  new Palette(      name: 'GREEN',      primary: Colors.green,      accent: Colors.greenAccent,      threshold: 500),  new Palette(      name: 'LIGHT GREEN',      primary: Colors.lightGreen,      accent: Colors.lightGreenAccent,      threshold: 600),  new Palette(      name: 'LIME',      primary: Colors.lime,      accent: Colors.limeAccent,      threshold: 800),  new Palette(      name: 'YELLOW', primary: Colors.yellow, accent: Colors.yellowAccent),  new Palette(name: 'AMBER', primary: Colors.amber, accent: Colors.amberAccent),  new Palette(      name: 'ORANGE',      primary: Colors.orange,      accent: Colors.orangeAccent,      threshold: 700),  new Palette(      name: 'DEEP ORANGE',      primary: Colors.deepOrange,      accent: Colors.deepOrangeAccent,      threshold: 400),  new Palette(name: 'BROWN', primary: Colors.brown, threshold: 200),  new Palette(name: 'GREY', primary: Colors.grey, threshold: 500),  new Palette(name: 'BLUE GREY', primary: Colors.blueGrey, threshold: 500),];class ColorItem extends StatelessWidget {  const ColorItem(      {Key key,      @required this.index,      @required this.color,      this.prefix = '',      this.onChangeTheme})      : assert(index != null),        assert(color != null),        assert(prefix != null),        super(key: key);  final int index;  final Color color;  final String prefix;  final Function(Color) onChangeTheme;  String colorString() =>      "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}";  @override  Widget build(BuildContext context) {    return new Semantics(      container: true,      child: new Container(        height: kColorItemHeight,        padding: const EdgeInsets.symmetric(horizontal: 16.0),        color: color,        child: new SafeArea(          top: false,          bottom: false,          child: FlatButton(            child: new Row(              mainAxisAlignment: MainAxisAlignment.spaceBetween,              crossAxisAlignment: CrossAxisAlignment.center,              children: <Widget>[                new Text('$prefix$index'),                new Text(colorString()),              ],            ),            onPressed: () {              onChangeTheme(color);            },          ),        ),      ),    );  }}class PaletteTabView extends StatelessWidget {  static const List<int> primaryKeys = const <int>[    50,    100,    200,    300,    400,    500,    600,    700,    800,    900  ];  static const List<int> accentKeys = const <int>[100, 200, 400, 700];  PaletteTabView({Key key, @required this.colors, this.onChangeTheme})      : assert(colors != null && colors.isValid),        super(key: key);  final Palette colors;  final Function(Color) onChangeTheme;  @override  Widget build(BuildContext context) {    final TextTheme textTheme = Theme.of(context).textTheme;    final TextStyle whiteTextStyle =        textTheme.body1.copyWith(color: Colors.white);    final TextStyle blackTextStyle =        textTheme.body1.copyWith(color: Colors.black);    final List<Widget> colorItems = primaryKeys.map((int index) {      return new DefaultTextStyle(        style: index > colors.threshold ? whiteTextStyle : blackTextStyle,        child: new ColorItem(            index: index,            color: colors.primary[index],            onChangeTheme: onChangeTheme),      );    }).toList();    if (colors.accent != null) {      colorItems.addAll(accentKeys.map((int index) {        return new DefaultTextStyle(          style: index > colors.threshold ? whiteTextStyle : blackTextStyle,          child: new ColorItem(              index: index,              color: colors.accent[index],              prefix: 'A',              onChangeTheme: onChangeTheme),        );      }).toList());    }    return new ListView(      itemExtent: kColorItemHeight,      children: colorItems,    );  }}class ThemeSelectPage extends StatelessWidget {  @override  Widget build(BuildContext context) {    return StoreConnector<AppState, _ViewModel>(        converter: _ViewModel.fromStore,        builder: (context, vm) {          return new DefaultTabController(            length: allPalettes.length,            child: new Scaffold(              appBar: new AppBar(                elevation: 0.0,                title: const Text("主题色"),                bottom: new TabBar(                  isScrollable: true,                  tabs: allPalettes                      .map((Palette swatch) => new Tab(text: swatch.name))                      .toList(),                ),              ),              body: new TabBarView(                children: allPalettes.map((Palette colors) {                  return new PaletteTabView(                    colors: colors,                    onChangeTheme: vm.onChangeTheme,                  );                }).toList(),              ),            ),          );        });  }}class _ViewModel {  final Function(Color) onChangeTheme;  _ViewModel({this.onChangeTheme});  static _ViewModel fromStore(Store<AppState> store) {    return _ViewModel(      onChangeTheme: (color) {        SharedPrfUtils.saveInt(SharedPrfKey.SP_KEY_THEME_COLOR, color.value);        store.dispatch(RefreshThemeDataAction(AppTheme.changeTheme(color)));      },    );  }}

运行成果

执行代码,成果如下