本文比較渣,有待重構....
git
今天的任務是將昨的代碼用redux整理一下。
在此以前先說統一幾個名詞在本文中的叫法。本文源碼見githubgithub
store : 倉庫
dispatch : 分發
action : 動做
reducer : 分解器
connector : 鏈接器
provider : 供應器
converter : 轉換器
builder : 構造器
依賴: flutter_redux: ^0.5.3
複製代碼
你們應該都還記得初始項目吧,下面是它的梳理圖,磨刀不誤砍柴工。
我打算從它開始入手,向你簡單介紹redux是什麼?redux
很簡單,行爲是點擊,變化是數字的自增加。
關於reducer,不想是什麼純不純,在我看來它就是一個獨立的邏輯單元,
不依靠外界存活,在邏輯上即可存在:給定一個輸入就會返回一個預期的輸出bash
enum Actions {
increment//定義增長行爲
}
//使用counterReducer將行爲從類中抽離分解,成爲獨立邏輯單元
int counterReducer(int input, dynamic action) {
var output;
switch(action){
case Actions.increment:
output=input+1;
break;
}
return output;
}
複製代碼
redux核心之一即是Store,是一個倉庫用來儲存,供應,分發。
返回一個倉庫提供器,它是一個Widget,須要store和child屬性。微信
class ReduxPage extends StatelessWidget {
final Store<int> store;
ReduxPage({Key key, this.store,}) : super(key: key);
return StoreProvider<int>(
store: store,
child: child,
);
}
複製代碼
這裏爲了看得清楚些,將
countText
與fab
兩個與狀態有關的組件抽離一下app
var child = MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text("Flutter Redux Demo"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'You have pushed the button this many time'
),
countText//顯示數字的Text
],
),
),
floatingActionButton: fab,//點擊的按鈕
),
);
複製代碼
顯示數字的Text:countText
若是你想要到倉庫拿東西,你須要什麼?鑰匙唄。
StoreConnector
倉庫鏈接器就是這把鑰匙
converter轉換器中回調出store對象,你就能夠經過store去取值了,經過構造器生成組件返回出去less
var countText= StoreConnector<int, String>(
converter: (store) => store.state.toString(),//轉換器,獲取倉庫,從倉庫拿值
builder: (context, count) {//構造器,構建Widget
return Text(
count,
style: Theme.of(context).textTheme.display1,
);
},
);
複製代碼
處理動做的按鈕
處理動做也是須要倉庫,使用進行分發(dispatch)相應動做(action)
在構造器中,你就可使用該動做邏輯了。ide
var fab= StoreConnector<int, VoidCallback>(
converter: (store) {
return () => store.dispatch(Actions.increment);//分發動做
},
builder: (context, callback) {//構造器,使用動做邏輯
return FloatingActionButton(
onPressed: callback,
tooltip: 'Increment',
child: Icon(Icons.add),
);
},
);
複製代碼
這裏將動做方成爲攻方,響應方成爲受方,下面的圖闡釋了兩方Widget如何構建post
能夠說這核心即是倉庫store了,看一下對象如何生成ui
void main() {
final store = Store<int>(counterReducer, initialState: 0);
runApp(ReduxPage(
store: store,
));
}
複製代碼
也許你會說:"感受也不咋地啊,感受好麻煩。"
好比我想要點一下加10該怎麼辦?使用redux你須要定義一個行爲,及響應。
在行爲分發時修改行爲便可。也許你說我不用redux,改行就好了。若是邏輯很是多怎麼辦
以後又要改回來怎麼辦?抽象出一個行爲來管理邏輯切換起來是很是方便的
並且想要修改直接在reducer中進行便可,就避免了污染封裝的組件源碼。
enum Actions{
increment,
increment10
}
int counterReducer(int input, dynamic action) {
var output;
switch(action){
case Actions.increment:
output=input+1;
break;
case Actions.increment10:
output=input+10;
break;
}
return output;
}
var fab= StoreConnector<int, VoidCallback>(
converter: (store) {
return () => store.dispatch(Actions.increment10);
},
複製代碼
另外一個界面如何輕鬆享有上個界面的數據,這是個很大的問題。
固然能夠經過構造傳參,但這顯然十分麻煩,不只亂,並且還要接收個參數。
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
class SecondPage extends StatelessWidget {
SecondPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
var text = StoreConnector<int, String>(//直接從狀態取值
converter: (store) => store.state.toString(),
builder: (context, count) {
return Text(
count.toString(),
style: Theme.of(context).textTheme.display1,
);
},
);
return Scaffold(
appBar: AppBar(
title: Text("SecondPage"),
),
body: Align(
alignment: Alignment.topCenter,
child: text,
),
);
}
}
複製代碼
ReduxPage中爲文字添加點擊跳轉到SecondPage
Builder _skipToSecondPage(StoreConnector<int, String> countText) {
return Builder(
builder: (context) =>
InkWell(child: countText, onTap: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (BuildContext context) {
return SecondPage();
}));
},),
);
}
複製代碼
仍是同樣的界面效果。
class Todo {
String sth; //待作事項
bool done;//是否已完成
Todo({this.sth, this.done}); //是否已作完
}
複製代碼
昨天分析了有個有三個狀態和四個動做
class TodoState {
List<Todo> todos; //列表數據
String text; //當前輸入文字
ShowType showType;//顯示類型
TodoState({this.todos, this.text, this.showType}); //顯示類型
}
enum Acts {
add, //添加到todo
selectAll, //篩選全部
selectTodo, //篩選待完成
selectDone, //篩選已完成
}
TodoState todoReducer(TodoState input, dynamic action) {
switch (action) {
case Acts.add:
if (input.text != null && input.text != "") {
input.todos.add(Todo(sth: input.text, done: false));
input.text = "";
}
break;
case Acts.selectAll:
input.showType=ShowType.all;
break;
case Acts.selectTodo:
input.showType=ShowType.todo;
break;
case Acts.selectDone:
input.showType=ShowType.done;
break;
}
return input;
}
final todoListStore = Store<TodoState>(todoReducer,
initialState://初始狀態
TodoState(todos: <Todo>[], text: "", showType: ShowType.all));
複製代碼
和上面同樣,使用StoreConnector來從倉庫拿資源,只不過這裏資源是TodoState對象
動做的話,做爲攻方,依舊是那回調來執行相應動做。
class TodoList extends StatefulWidget {
final Store<TodoState> store;
TodoList({
Key key,
this.store,
}) : super(key: key);
@override
_TodoListState createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
@override
Widget build(BuildContext context) {
var textField= StoreConnector<TodoState, TodoState>(
converter: (store) =>store.state,//轉換器,獲取倉庫,從倉庫拿值
builder: (context, state) {//構造器,構建Widget
return TextField(
controller: TextEditingController(text: state.text),
keyboardType: TextInputType.text,
textAlign: TextAlign.start,
maxLines: 1,
cursorColor: Colors.black,
cursorWidth: 3,
style: TextStyle(
fontSize: 16, color: Colors.lightBlue, backgroundColor: Colors.white),
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: '添加一個待辦項',
hintStyle: TextStyle(color: Colors.black26, fontSize: 14),
contentPadding: EdgeInsets.only(left: 14.0, bottom: 8.0, top: 8.0),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.white),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
),
),
onChanged: (str){
state.text=str;
},
);
},
);
var btn = StoreConnector<TodoState, VoidCallback>(
converter:(store) {
return () => store.dispatch(Acts.add);//分發動做
},
builder: (context, callback) {
return RaisedButton(
child: Icon(Icons.add),
padding: EdgeInsets.zero,
onPressed: (){
callback();
FocusScope.of(context).requestFocus(FocusNode());
});
},
);
var inputBtn = Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
child: textField,
width: 200,
),
ClipRRect(
borderRadius: BorderRadius.only(
topRight: Radius.circular(10), bottomRight: Radius.circular(10)),
child: Container(
child: btn,
width: 36,
height: 36,
),
),
],
);
var listInfo = [
["所有", Acts.selectAll],
["已完成", Acts.selectDone],
["未完成", Acts.selectTodo],
];
var op = Row(//操做按鈕
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: listInfo.map((e) {
return StoreConnector<TodoState, VoidCallback>(
converter: (store) {
return () => store.dispatch(e[1]);
},
builder: (context, callback) {
return RaisedButton(
onPressed: callback,
child: Text(e[0]),
color: Colors.blue,
);
},
);
}).toList(),
);
var listView = StoreConnector<TodoState, TodoState>(
converter: (store) => store.state, //轉換器,獲取倉庫,從倉庫拿值
builder: (context, state) {
var result;
//構造器,構建Widget
switch(state.showType){
case ShowType.all:
result= formList(state.todos);
break;
case ShowType.todo:
result= formList(List.of( state.todos.where((e)=>!e.done)));
break;
case ShowType.done:
result= formList(List.of( state.todos.where((e)=>e.done)));
break;
}
return result;
});
return StoreProvider<TodoState>(
store: widget.store,
child: Column(
children: <Widget>[inputBtn, op, Expanded(child: listView)],
),
);
}
Widget formList(List<Todo> todos) {
return ListView.builder(
itemCount: todos.length,
padding: EdgeInsets.all(8.0),
itemExtent: 50.0,
itemBuilder: (BuildContext context, int index) {
var key = todos[index].sth;
var value = todos[index].done;
var text = Align(
child: Text(
key,
style: TextStyle(
decorationThickness: 3,
decoration:
value ? TextDecoration.lineThrough : TextDecoration.none,
decorationColor: Colors.blue),
),
alignment: Alignment.centerLeft,
);
return Card(
child: Row(
children: <Widget>[
Checkbox(
onChanged: (b) {
todos[index].done = b;
setState(() {});
},
value: todos[index].done,
),
text
],
),
);
},
);
}
}
複製代碼
能夠看到StoreConnector中有兩個泛型,其中第二個命名爲ViewModel
如今實現一下,在已完成和未完成按鈕點擊後將CheckBox隱藏,這時就很是方便了。
--->[1.加入一個狀態]----
class TodoState {
List<Todo> todos; //列表數據
String text; //當前輸入文字
ShowType showType;//顯示類型
bool showBox=true;//是否顯示checkBox
TodoState({this.todos, this.text, this.showType,this.showBox}); //顯示類型
}
--->[2.在todoReducer中直接修改showBox狀態]----
case Acts.selectAll:
input.showBox=true;
input.showType=ShowType.all;
break;
case Acts.selectTodo:
input.showType=ShowType.todo;
input.showBox=false;
break;
case Acts.selectDone:
input.showType=ShowType.done;
input.showBox=false;
break;
---->[提取出checkBox組件代碼]----
var checkBox=StoreConnector<TodoState, CheckBoxViewModel>(
converter: (store) {
return CheckBoxViewModel(store,index);
},
builder: (context, model) {
return Offstage(child:Checkbox(
value: model.done,
onChanged: model.onClick) ,
offstage: !model.store.state.showBox,) ;
},
);
---->[定義CheckBox模型]----
class CheckBoxViewModel {
final Store store;
final int index;
void Function(bool check) onClick;
CheckBoxViewModel(this.store,this.index) {
onClick = (check) {
store.dispatch(Acts.check);
store.state.todos[index].done = check;
};
}
get done{
return store.state.todos[index].done;
}
}
複製代碼
這樣就能夠很容易對狀態進行更改,不然,要在TodoList中直接改會很費勁。
本文到此接近尾聲了,若是想快速嚐鮮Flutter,《Flutter七日》會是你的必備佳品;若是想細細探究它,那就跟隨個人腳步,完成一次Flutter之旅。
另外本人有一個Flutter微信交流羣,歡迎小夥伴加入,共同探討Flutter的問題,本人微信號:zdl1994328
,期待與你的交流與切磋。