ScopedModel
屬於入門級別的狀態管理框架,它的思想比較簡單,參考官方文檔即可以很容易理解其中構架。git
在Flutter
中Lifting state up
(狀態提高)是十分必要的,狀態提高能夠理解爲把組件之間相互共享的狀態提取出來放在一個較高層級中管理的一種思想。ScopedModel
提供了對於這種狀態管理的便利。github
ScopedModel
主要有三個重要的概念,也是其中的三個類:Model
、ScopedModel
和ScopedModelDescendant
。ScopedModel
基本上經過這三個類實現其功能。bash
Model
是封裝狀態和狀態操做的地方。咱們能夠將想要的數據存放在Model
當中而且將對數據操做,如添加刪除的相關方法放在這裏。Model
還提供了一個notifyListeners()
方法,它的做用是當數據發生改變時,能夠經過調用notifyListeners()
方法通知界面進行更新。網絡
ScopedModel
是一個用於保存Model
的Widget
。一般ScopedModel
會一個應用的入口處做爲父佈局使用,並以Model
做爲參數傳入,使得ScopedModel
持有Model
。app
在ScopedModel
的子佈局中,能夠經過ScopedModel.of<Model>(context)
方法來獲取Model
。框架
ScopedModelDescendant
,顧名思義,是ScopedModel
的派生物。一樣的,它也是一個Widget
。ScopedModelDescendant
會做爲ScopedModel
下的子佈局存在,它的主要做用是響應狀態更新。async
ScopedModelDescendant
中存在builder
函數,這個函數會在Model
的notifyListeners()
發生時被調用,從而根據Model
中的數據生成相應的界面。ide
這裏以常見的獲取列表選擇列表爲例子。一個頁面用於展現選中項和跳轉到列表,一個頁面用於顯示列表。函數
scoped_model
第三方庫在根目錄的pubspec.yaml
文件的dependencies
中加入依賴佈局
dependencies:
...
scoped_model: ^1.0.0
複製代碼
Model
建立一個ListModel
類,這個類須要繼承scoped_model
包裏的Model
類。
ListModel
類中包含三個狀態:列表初始化標誌、列表數據、選中的列表項。
bool _init = false; // 列表初始化標誌
List<String> _list = []; // 列表數據
String _selected = '未選中'; // 選中的列表項
複製代碼
在Model
中不只只有數據,還包括對數據操做的方法,這裏定義兩個操做方法,分別是選中列表項目和加載列表的方法,而且,這兩個方法在更新數據後,須要調用notifyListeners()
通知UI更新。
/**
* 選中列表項
*/
void select(String selected) {
_selected = selected;
// 通知數據變動
notifyListeners();
}
/**
* 加載列表
*/
void loadList() async {
// 模擬網絡請求
await Future.delayed(Duration(milliseconds: 3000));
_list = [
'1. Scoped Model',
'2. Scoped Model',
'3. Scoped Model',
'4. Scoped Model',
'5. Scoped Model',
'6. Scoped Model',
'7. Scoped Model',
'8. Scoped Model',
'9. Scoped Model',
'10. Scoped Model'
];
_init = true;
// 通知數據變動
notifyListeners();
}
複製代碼
在UI上,使用ScopedModel
做爲根佈局,提供Model
,使用ScopedModelDescendant
做爲子佈局,響應Model
。
首先,在main()
方法中,建立ListModel
實例,用ScopedModel
包裹MyApp佈局
void main() {
// 建立Model實例
ListModel listModel = ListModel();
// 使用ScopedModel做爲根佈局
runApp(ScopedModel(model: listModel, child: MyApp()));
}
複製代碼
爲了體現狀態提高這一律念,例子中使用兩個頁面,一個是ShowPage
,另外一個是ListPage
。ShowPage
用於顯示選中的列表項目和提供跳轉到ListPage
的入口,ListPage
用於加載顯示列表。
在ShowPage
中,顯示ListModel
中的選中項。
ScopedModelDescendant<ListModel>(
builder: (context, child, model) {
String selected = model.selected;
return Text(selected);
}
),
複製代碼
ScopedModelDescendant
的泛型指明ListModel
,它便會自動獲取ScopedModel
中的ListModel
,在builder: (context, child, model)
中便可經過其中的model
參數獲取狀態,構建UI。
一樣的,在ListPage
中,經過ScopedModelDescendant
來顯示加載狀態和列表。
body: ScopedModelDescendant<ListModel>(builder: (context, child, model) {
// 根據狀態顯示界面
if (!model.isInit) {
// 顯示loading界面
return buildLoad();
} else {
// 顯示列表界面
var list = model.list;
return buildList(list);
}
}),
複製代碼
ListPage
是一個StatefulWidget
,因此能夠在initState()
方法中進行列表的加載工做。
@override
void initState() {
super.initState();
ListModel model = ScopedModel.of<ListModel>(context); // 獲取ListModel
if (!model.isInit) {
model.loadList(); // 加載列表
}
}
複製代碼
點擊列表中的某一項時,會選中該項,這時調用ListModel
中的select(String)
方法,並返回上一個界面。
onTap: () {
ListModel model = ScopedModel.of<ListModel>(context);
model.select(list[index]);
// 返回到上一級頁面
Navigator.pop(context);
},
複製代碼
完整代碼能夠參考github.com/windinwork/…
ScopedModelDescendant
的層級須要儘可能低,能夠避免大範圍的UI重建。這裏引用官方的例子。// 錯誤示例
return ScopedModelDescendant<CartModel>(
builder: (context, child, cart) {
return HumongousWidget(
// ...
child: AnotherMonstrousWidget(
// ...
child: Text('Total price: ${cart.totalPrice}'),
),
);
},
);
複製代碼
// 正確示例
return HumongousWidget(
// ...
child: AnotherMonstrousWidget(
// ...
child: ScopedModelDescendant<CartModel>(
builder: (context, child, cart) {
return Text('Total price: ${cart.totalPrice}');
},
),
),
);
複製代碼
ScopedModel
功能比較簡單,使用Model
保存狀態和通知狀態改變,使用ScopedModel
提供Model
,使用ScopedModelDescendant
佈局來響應狀態變化,是一個十分適合入門者理解的狀態管理模型。