手把手入門Fish-Redux開發(fā)flutter(下)

手把手入門Fish-Redux開發(fā)flutter(上)
手把手入門Fish-Redux開發(fā)flutter(中)
手把手入門Fish-Redux開發(fā)flutter(下)

前面兩篇欣孤,我們了解了fish-redux并實(shí)現(xiàn)了簡單的功能,這次我們再了解fish-redux一些其他的特點(diǎn)昔逗〉冀郑看一下結(jié)果圖:

image

1 使用 Component 和 Adapter 做一個(gè)列表

1.1 創(chuàng)建列表頁、定義數(shù)據(jù)

頁面創(chuàng)建的過程跟之前一樣纤子,就省略啦搬瑰。我創(chuàng)建了名為 List 的頁面,結(jié)果如下


image

在 app.dart 中加入我們的這個(gè)頁面控硼。修改過的 app.dart 如下

Widget createApp() {
  final AbstractRoutes routes = PageRoutes(
    pages: <String, Page<Object, dynamic>>{
      'entrance_page': EntrancePage(),
      'grid_page': GridPage(),
      'list_page':ListPage(),   //此處加入我們新建的頁面
    },
  );
//省略...

然后實(shí)現(xiàn)從 Grid 頁面跳轉(zhuǎn)到這個(gè)頁面泽论,(頁面跳轉(zhuǎn)上一篇講過了,這里不詳細(xì)說卡乾。就是創(chuàng)建action翼悴、在view中dispatch action、effect中接收到action并跳轉(zhuǎn)頁面)代碼如下


image
image
image

1.2 創(chuàng)建 component

然后我們在 list 包下面創(chuàng)建一個(gè) Component 作為列表的每個(gè) item 幔妨。

第一步 通過插件新建Component

首先創(chuàng)建一個(gè)名為 item 的包鹦赎,然后在 item 下創(chuàng)建FishReduxTemplate,這次我們選擇 Component误堡。

image

image

創(chuàng)建結(jié)果如下古话,可以看到組件中的 component.dart 類似頁面中的 page.dart。


image

第二步 定義組件數(shù)據(jù)和ui

我們給 state 三個(gè)字段 type(圖標(biāo)的形狀)锁施,title(標(biāo)題)陪踩,content(內(nèi)容)。修改 /list/item/state.dart 如下

import 'package:fish_redux/fish_redux.dart';

class ItemState implements Cloneable<ItemState> {

  int type;
  String title;
  String content;

  ItemState({this.type, this.title, this.content});

  @override
  ItemState clone() {
    return ItemState()
    ..type = type
    ..title = title
    ..content = content;
  }
}

ItemState initState(Map<String, dynamic> args) {
  return ItemState();
}

然后我們來實(shí)現(xiàn) item 的視圖悉抵,使用上面 state 的數(shù)據(jù)肩狂,并且根據(jù) type 不同顯示不同的 icon 圖標(biāo)(詳見注釋)。/list/item/view.dart 如下

import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';

import 'action.dart';
import 'state.dart';

Widget buildView(ItemState state, Dispatch dispatch, ViewService viewService) {
  return Container(
    margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
    height: 120.0,
    color: Colors.white,
    child: GestureDetector(
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          //左側(cè)圖標(biāo)
          Container(
            padding: const EdgeInsets.only(right: 5.0),
            child: Center(
              child: Icon(
              //不同type顯示不同icon
                state.type == 1 ? Icons.account_circle : Icons.account_box,
                size: 50.0,
              ),
            ),
          ),
          //右側(cè)
          Expanded(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                //標(biāo)題部分
                Container(
                  height: 30,
                  child: Text(
                    state.title,
                    style: TextStyle(fontSize: 22.0),
                  ),
                ),
                //內(nèi)容部分
                Text(
                  state.content,
                  style: TextStyle(fontSize: 16.0),
                ),
              ],
            ),
          ),
        ],
      ),
      onTap: () {
        //todo 點(diǎn)擊事件
      },
    ),
  );
}

1.3 關(guān)聯(lián)組件和頁面

Component告一段落姥饰。接著我們在列表中使用組件傻谁。

第一步 創(chuàng)建adapter

首先在 List 的 State 中存儲(chǔ)每一個(gè) Item 的 State 數(shù)據(jù)。/list/state.dart/ 如下

import 'package:fish_demo/list/item/state.dart';
import 'package:fish_redux/fish_redux.dart';

class ListState implements Cloneable<ListState> {
  
  List<ItemState> items;    //保存item的state

  @override
  ListState clone() {
    return ListState()
    ..items = items;
  }
}

ListState initState(Map<String, dynamic> args) {
  return ListState();
}

然后把 list 和 item 關(guān)聯(lián)列粪,我們使用 fish-redux 的一個(gè)組件叫 adapter审磁。首先在 list 包下用插件創(chuàng)建 FishReduxTemplate薛耻,然后選中 DynamicFlowAdapter呜魄,并取消其他的勾選路狮,取名 List尝丐,如下

image

第二步 實(shí)現(xiàn)connector

看到默認(rèn)的 adapter 代碼中為我們準(zhǔn)備了兩個(gè)類:一個(gè) ListAdapter 和一個(gè) _ListConnector掺逼。其中 ListAdapter 中我們配置需要關(guān)聯(lián)的組件吃媒,即把組件添加到 pool。以及列表和組件的數(shù)據(jù)適配關(guān)系吕喘,通過實(shí)現(xiàn)一個(gè) connector 赘那。完成的 /list/adapter.dart 如下

import 'package:fish_demo/list/state.dart';
import 'package:fish_redux/fish_redux.dart';

import 'item/component.dart';
import 'item/state.dart';

class ListAdapter extends DynamicFlowAdapter<ListState> {
  ListAdapter()
      : super(
          pool: <String, Component<Object>>{
            "MyItem": ItemComponent(),  //引用組件
          },
          connector: _ListConnector(),
        );
}

class _ListConnector extends ConnOp<ListState, List<ItemBean>> {
  @override
  List<ItemBean> get(ListState state) {
    //判斷ListState里面的items數(shù)據(jù)是否為空
    if (state.items?.isNotEmpty == true) {
      //若不為空,把item數(shù)據(jù)轉(zhuǎn)化成ItemBean的列表
      return state.items
        .map<ItemBean>((ItemState data) => ItemBean('MyItem', data))
        .toList(growable: true);
    }else{
      //若為空氯质,返回空列表
      return <ItemBean>[];
    }
  }

  @override
  void set(ListState state, List<ItemBean> items) {
    //把ItemBean的變化募舟,修改到item的state的過程
    if (items?.isNotEmpty == true) {
      state.items = List<ItemState>.from(
        items.map<ItemState>((ItemBean bean) => bean.data).toList());
    } else {
      state.items = <ItemState>[];
    }
  }

  @override
  subReducer(reducer) {
    // TODO: implement subReducer
    return super.subReducer(reducer);
  }
}

第三步 把a(bǔ)dapter添加到列表頁的依賴中

我們打開 List 頁面的 page 文件,然后吧adapter添加到頁面的 dependencies 中闻察。如下

import 'package:fish_demo/list/adapter.dart';//注意1
import 'package:fish_redux/fish_redux.dart' hide ListAdapter;//注意1

import 'effect.dart';
import 'reducer.dart';
import 'state.dart';
import 'view.dart';

class ListPage extends Page<ListState, Map<String, dynamic>> {
  ListPage()
      : super(
            initState: initState,
            effect: buildEffect(),
            reducer: buildReducer(),
            view: buildView,
            dependencies: Dependencies<ListState>(
                adapter: NoneConn<ListState>() + ListAdapter(),//注意2
                slots: <String, Dependent<ListState>>{
                }),
            middleware: <Middleware<ListState>>[
            ],);

}

上面的代碼我標(biāo)注了兩點(diǎn)注意拱礁。

注意1:我們這里引用的是剛才自定義的ListAdapter而不是fish-redux自帶的ListAdapter類,這里要處理一下引用辕漂。(早知道就起名字的時(shí)候就不叫ListAdapter了呢灶。。钉嘹。)

注意2:這里使用的"加號"是 fish-redux 重載的操作符鸯乃。后者是我們自定義的 adapter ,而由于我們在外層已經(jīng)不需要使用 connector跋涣,所以前者傳入一個(gè) NoneConn 缨睡。

第四步 列表ui

最后我們在 /list/view.dart 獲取到 adapter 并完成列表頁的UI。/list/view.dart 如下

import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';
import 'state.dart';

Widget buildView(ListState state, Dispatch dispatch, ViewService viewService) {
  
  ListAdapter adapter = viewService.buildAdapter();     //創(chuàng)建adapter

  return Scaffold(
      appBar: new AppBar(
        title: new Text('列表頁'),
      ),
      body: Container(
        child: ListView.builder(
          itemBuilder: adapter.itemBuilder, //把a(bǔ)dapter配置到list
          itemCount: adapter.itemCount,     //
        ),
      ));
}

1.4 配上假數(shù)據(jù)

最后我們給 List 頁面配上一些假數(shù)據(jù)來看看效果陈辱。具體過程我們已經(jīng)學(xué)過了

  1. effect 在頁面初始化時(shí)創(chuàng)建假數(shù)據(jù)
  2. effect 把發(fā)送攜帶假數(shù)據(jù)的 action
  3. reducer 接收 action奖年,通過假數(shù)據(jù)更新state

大家基本都會(huì)了,要注意一點(diǎn)這次 action 攜帶了參數(shù)沛贪。貼一下代碼


image

image

image

最后運(yùn)行一下看看效果

image

2 使用全局 store 更換主題

這次我們來接觸一下 store拾并。它負(fù)責(zé)管理全局的狀態(tài),我們以主題顏色為例進(jìn)行演示鹏浅。

2.1 創(chuàng)建全局state

之前頁面和組件的創(chuàng)建都是通過插件模板嗅义,store 這次手動(dòng)創(chuàng)建。
創(chuàng)建名為 store 的 package隐砸。然后創(chuàng)建一個(gè) state.dart之碗,我們用它來保存全局狀態(tài)。本例中就是保存主題顏色季希。/store/state.dart 如下

import 'dart:ui';
import 'package:fish_redux/fish_redux.dart';

abstract class GlobalBaseState {
  Color get themeColor;
  set themeColor(Color color);
}

class GlobalState implements GlobalBaseState, Cloneable<GlobalState> {
  @override
  Color themeColor;

  @override
  GlobalState clone() {
    return GlobalState();
  }
}

注意這里我們先定義了一個(gè)抽象類 GlobalBaseState 包含 themeColor 字段褪那。一會(huì)兒我們會(huì)讓所有頁面的 state 繼承它幽纷。另外上面我們還寫了一個(gè)它的實(shí)現(xiàn)類 GlobalState,即全局的 state博敬。

2.2 創(chuàng)建store

我們再新建一個(gè) store.dart友浸,作為 App的Store。/store/store.dart 如下

import 'package:fish_redux/fish_redux.dart';
import 'state.dart';

class GlobalStore {
  static Store<GlobalState> _globalStore;

  static Store<GlobalState> get store =>
    _globalStore ??= createStore<GlobalState>(GlobalState(), buildReducer());

}

可以看到 store 保存了全局 state偏窝,并且它也能擁有 reducer 來處理事件收恢。接下來我們就定義它的 action 和 reducer。

2.3 創(chuàng)建store的action和reducer

創(chuàng)建 action.dart 祭往,定義一個(gè)改變主題顏色的事件伦意。/store/action.dart 如下

import 'package:fish_redux/fish_redux.dart';

enum GlobalAction { changeThemeColor }

class GlobalActionCreator {
  static Action onchangeThemeColor() {
    return const Action(GlobalAction.changeThemeColor);
  }
}

創(chuàng)建 reducer.dart,接收事件并修改 GlobalState 的主題色硼补。(這里我們讓主題色在藍(lán)色和綠色中來回切換)/store/reducer.dart 如下

import 'dart:ui';
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart' hide Action;
import 'action.dart';
import 'state.dart';

Reducer<GlobalState> buildReducer() {
  return asReducer(
    <Object, Reducer<GlobalState>>{
      GlobalAction.changeThemeColor: _onchangeThemeColor,
    },
  );
}

GlobalState _onchangeThemeColor(GlobalState state, Action action) {
  final Color color =
  state.themeColor == Colors.green ? Colors.blue : Colors.green;
  return state.clone()..themeColor = color;
}

這樣整個(gè) store 就寫好了驮肉。全家福


image

2.4 繼承GlobalBaseState

我們讓所有頁面的 state 繼承 GlobalBaseState,并在頁面視圖中吧標(biāo)題欄的顏色設(shè)置為 themeColor已骇。我們一共有三個(gè)界面(entrance离钝、grid、list)需要修改褪储,以 list 頁面為例奈辰,首先是 /list/state.dart

image

然后是 /list/view.dart

image

其他兩個(gè)頁面同理,不一一展示了乱豆。

2.5 關(guān)聯(lián)state

如何把各個(gè)頁面的 state 和全局 GlobalState 聯(lián)系起來呢奖恰?我們要在 app.dart 中配置 visitor。在這里宛裕,我們判斷頁面是否繼承了 GlobalBaseState瑟啃,然后跟全局 store 建立聯(lián)系,即該頁面的 state 隨全局state更新而更新揩尸。修改好的 app.dart如下

Widget createApp() {
  final AbstractRoutes routes = PageRoutes(
    pages: <String, Page<Object, dynamic>>{
      'entrance_page': EntrancePage(),
      'grid_page': GridPage(),
      'list_page': ListPage(),
    },
    visitor: (String path, Page<Object, dynamic> page) {
      /// 滿足條件 Page<T> 蛹屿,T 是 GlobalBaseState 的子類。
      if (page.isTypeof<GlobalBaseState>()) {
        /// 建立 AppStore 驅(qū)動(dòng) PageStore 的單向數(shù)據(jù)連接
        /// 1. 參數(shù)1 AppStore
        /// 2. 參數(shù)2 當(dāng) AppStore.state 變化時(shí), PageStore.state 該如何變化
        page.connectExtraStore<GlobalState>(
          GlobalStore.store, (Object pagestate, GlobalState appState) {
          final GlobalBaseState p = pagestate;
          if (p.themeColor != appState.themeColor) {
            if (pagestate is Cloneable) {
              final Object copy = pagestate.clone();
              final GlobalBaseState newState = copy;
              newState.themeColor = appState.themeColor;
              return newState;
            }
          }
          return pagestate;
        });
      }
    },
  );
//以下省略...
}

2.6 觸發(fā)主題修改

最后我希望通過點(diǎn)擊 List 頁面的 item岩榆,來實(shí)現(xiàn)主題色的切換错负。

第一步

首先我們定義一個(gè) action,/list/item/action.dart 如下

import 'package:fish_redux/fish_redux.dart';

enum ItemAction { action, onThemeChange }

class ItemActionCreator {
  static Action onAction() {
    return const Action(ItemAction.action);
  }

  static Action onThemeChange() {
    return const Action(ItemAction.onThemeChange);
  }
}

第二步

我們在 item 點(diǎn)擊時(shí)勇边,發(fā)送這個(gè)事件犹撒。/list/item/view.dart 如下


Widget buildView(ItemState state, Dispatch dispatch, ViewService viewService) {
  return Container(
    margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
    height: 120.0,
    color: Colors.white,
    child: GestureDetector(
      child: Row(
        //省略...
      ),
      onTap: () {
        dispatch(ItemActionCreator.onThemeChange());
      },
    ),
  );
}

第三步

在 effect 接收事件,并發(fā)送我們之前定義的全局修改主題色事件粒褒。/list/item/effect.dart 如下

import 'package:fish_demo/store/action.dart';
import 'package:fish_demo/store/store.dart';
import 'package:fish_redux/fish_redux.dart';
import 'action.dart';
import 'state.dart';

Effect<ItemState> buildEffect() {
  return combineEffects(<Object, Effect<ItemState>>{
    ItemAction.action: _onAction,
    ItemAction.onThemeChange: _onThemeChange,
  });
}

void _onAction(Action action, Context<ItemState> ctx) {
}

void _onThemeChange(Action action, Context<ItemState> ctx) {
  GlobalStore.store.dispatch(GlobalActionCreator.onchangeThemeColor());
}

最后運(yùn)行看下效果:
一步步進(jìn)入列表頁识颊,多次點(diǎn)擊item,主題色隨之變化奕坟。返回上級頁面祥款,主題色也被改變清笨。

image

總結(jié)

至此,我們了解了 fish-redux 的一些基本的使用刃跛。由于客戶端同學(xué)對 redux 類框架接觸不多抠艾,所以在使用時(shí)要轉(zhuǎn)變思路、多看文檔桨昙、多思考检号。

項(xiàng)目源碼 https://github.com/Jegaming/FishReduxDemo

??如果我的內(nèi)容對您有幫助,歡迎點(diǎn)贊绊率、評論、轉(zhuǎn)發(fā)究履、收藏滤否。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市最仑,隨后出現(xiàn)的幾起案子藐俺,更是在濱河造成了極大的恐慌,老刑警劉巖泥彤,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件欲芹,死亡現(xiàn)場離奇詭異,居然都是意外死亡吟吝,警方通過查閱死者的電腦和手機(jī)菱父,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剑逃,“玉大人浙宜,你說我怎么就攤上這事∮蓟牵” “怎么了粟瞬?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長萤捆。 經(jīng)常有香客問我裙品,道長,這世上最難降的妖魔是什么俗或? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任市怎,我火速辦了婚禮,結(jié)果婚禮上辛慰,老公的妹妹穿的比我還像新娘焰轻。我一直安慰自己,他們只是感情好昆雀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布辱志。 她就那樣靜靜地躺著蝠筑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪揩懒。 梳的紋絲不亂的頭發(fā)上什乙,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機(jī)與錄音已球,去河邊找鬼臣镣。 笑死,一個(gè)胖子當(dāng)著我的面吹牛智亮,可吹牛的內(nèi)容都是我干的忆某。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼阔蛉,長吁一口氣:“原來是場噩夢啊……” “哼弃舒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起状原,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤聋呢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后颠区,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體削锰,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年毕莱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了器贩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡朋截,死狀恐怖磨澡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情质和,我是刑警寧澤稳摄,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站饲宿,受9級特大地震影響厦酬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瘫想,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一仗阅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧国夜,春花似錦减噪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽醋闭。三九已至,卻和暖如春朝卒,著一層夾襖步出監(jiān)牢的瞬間证逻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工抗斤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留囚企,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓瑞眼,卻偏偏與公主長得像龙宏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子伤疙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容