手把手入門Fish-Redux開發(fā)flutter(上)
手把手入門Fish-Redux開發(fā)flutter(中)
手把手入門Fish-Redux開發(fā)flutter(下)
前面兩篇欣孤,我們了解了fish-redux并實(shí)現(xiàn)了簡單的功能,這次我們再了解fish-redux一些其他的特點(diǎn)昔逗〉冀郑看一下結(jié)果圖:
1 使用 Component 和 Adapter 做一個(gè)列表
1.1 創(chuàng)建列表頁、定義數(shù)據(jù)
頁面創(chuàng)建的過程跟之前一樣纤子,就省略啦搬瑰。我創(chuàng)建了名為 List 的頁面,結(jié)果如下
在 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)頁面)代碼如下
1.2 創(chuàng)建 component
然后我們在 list 包下面創(chuàng)建一個(gè) Component 作為列表的每個(gè) item 幔妨。
第一步 通過插件新建Component
首先創(chuàng)建一個(gè)名為 item 的包鹦赎,然后在 item 下創(chuàng)建FishReduxTemplate,這次我們選擇 Component误堡。
創(chuàng)建結(jié)果如下古话,可以看到組件中的 component.dart 類似頁面中的 page.dart。
第二步 定義組件數(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尝丐,如下
第二步 實(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é)過了
- effect 在頁面初始化時(shí)創(chuàng)建假數(shù)據(jù)
- effect 把發(fā)送攜帶假數(shù)據(jù)的 action
- reducer 接收 action奖年,通過假數(shù)據(jù)更新state
大家基本都會(huì)了,要注意一點(diǎn)這次 action 攜帶了參數(shù)沛贪。貼一下代碼
最后運(yùn)行一下看看效果
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 就寫好了驮肉。全家福
2.4 繼承GlobalBaseState
我們讓所有頁面的 state 繼承 GlobalBaseState,并在頁面視圖中吧標(biāo)題欄的顏色設(shè)置為 themeColor已骇。我們一共有三個(gè)界面(entrance离钝、grid、list)需要修改褪储,以 list 頁面為例奈辰,首先是 /list/state.dart
然后是 /list/view.dart
其他兩個(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,主題色隨之變化奕坟。返回上級頁面祥款,主題色也被改變清笨。
總結(jié)
至此,我們了解了 fish-redux 的一些基本的使用刃跛。由于客戶端同學(xué)對 redux 類框架接觸不多抠艾,所以在使用時(shí)要轉(zhuǎn)變思路、多看文檔桨昙、多思考检号。
項(xiàng)目源碼 https://github.com/Jegaming/FishReduxDemo
??如果我的內(nèi)容對您有幫助,歡迎點(diǎn)贊绊率、評論、轉(zhuǎn)發(fā)究履、收藏滤否。