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
布局来响应状态变化,是一个十分适合入门者理解的状态管理模型。