Scoped_Model使用步骤:
1.定义 Model 的实现,如 CountModel ,并且在状态改变时执行 notifyListeners() 方法。
import 'package:flutter/cupertino.dart';
import 'package:scoped_model/scoped_model.dart';
class CountModel extends Model {
static CountModel of(BuildContext context) =>
ScopedModel.of<CountModel>(context);
int _count = 0;
int get count => _count;
void add() {
_count++;
notifyListeners();
}
}
2.使用 ScopedModel 在父Widget 中加载 Model ,然后使用 ScopedModelDescendant 或者 ScopedModel.of(context) 加载 Model 内状态数据来实现子widget共享同一个CountModel状态。
以下就是两个子widget共享一个count的状态,子1widget点击按钮使count加一,子2widget显示的count也会同时变化加一,而父widget不会改变,。
class ScopedPage extends StatelessWidget {
final CountModel _model = new CountModel();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text("scoped"),
),
body: Container(
child: new ScopedModel<CountModel>(
model: _model,
//child: CountWidget(),
child: Column(
children: [
//Text("父widget:"+_model.count.toString()),
// ScopedModelDescendant<CountModel>(
// builder: (context,child,model){
// return Text("父widget:"+_model.count.toString());
// }
// ),
CountWidget(),
CountWidget_2(),
],
),
),
));
}
}
class CountWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new ScopedModelDescendant<CountModel>(
builder: (context, child, model) {
return new Column(
children: <Widget>[
new Container(child: new Center(child: new Text("子1widget:"+model.count.toString()))),
new Row(
children: [
new FlatButton(
onPressed: () {
model.add();
},
color: Colors.blue,
child: new Text("+")),
// new FlatButton(
// onPressed: () {
// Navigator.push(context, MaterialPageRoute(builder:(context)=>new SM_page4()));
// },
// color: Colors.blue,
// child: new Text("go")),
],
),
],
);
});
}
}
class CountWidget_2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new ScopedModelDescendant<CountModel>(
builder: (context, child, model) {
return new Column(
children: <Widget>[
new Container(child: new Center(child: new Text("子2widget:"+model.count.toString()))),
new Row(
children: [
new FlatButton(
onPressed: () {
model.add();
},
color: Colors.blue,
child: new Text("+")),
// new FlatButton(
// onPressed: () {
// Navigator.push(context, MaterialPageRoute(builder:(context)=>new SM_page4()));
// },
// color: Colors.blue,
// child: new Text("go")),
],
),
],
);
});
}
}
效果图:
在实践中发现的几个点:
1.即使在父widget里面的小组件要共享model的状态,也要用ScopedModelDescendant包裹起来,不然不能共享model的状态
//还是原来的代码
class ScopedPage extends StatelessWidget {
final CountModel _model = new CountModel();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text("scoped"),
),
body: Container(
child: new ScopedModel<CountModel>(
model: _model,
//child: CountWidget(),
child: Column(
children: [
//不加ScopedModelDescendant
Text("不加ScopedModelDescendant父widget:"+_model.count.toString()),
//加了ScopedModelDescendant
ScopedModelDescendant<CountModel>(
builder: (context,child,model){
return Text("加了ScopedModelDescendant父widget:"+_model.count.toString());
}
),
CountWidget(),
CountWidget_2(),
],
),
),
));
}
}
2.ScopedModel能不能跨页面共享model状态?
可以
但是如果想只是用Navigator来跳转到新页面,然后对新页面使用ScopedModelDescendant是会报错的,就像这样:
class CountWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new ScopedModelDescendant<CountModel>(
builder: (context, child, model) {
return new Column(
children: <Widget>[
new Container(child: new Center(child: new Text("子1widget:"+model.count.toString()))),
new Row(
children: [
new FlatButton(
onPressed: () {
model.add();
},
color: Colors.blue,
child: new Text("+")),
new FlatButton(
onPressed: () {
//就像这样
Navigator.push(context, MaterialPageRoute(builder:(context)=>new ScopedPage2()));
},
color: Colors.blue,
child: new Text("go")),
],
),
],
);
});
}
}
class ScopedPage2 extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return new ScopedPage2State();
}
}
class ScopedPage2State extends State<ScopedPage2> {
@override
Widget build(BuildContext context) {
// TODO: implement build
return ScopedModelDescendant<CountModel>(
builder: (context,child,model){
return Scaffold(
appBar:AppBar (title:Text("SM_3:")),
body: Column(
children: [
Text("子widget3:"+model.count.toString()),
FlatButton(
onPressed: () {
model.add();
},
color: Colors.blue,
child: new Text("+"))
],
),
);
}
);
}
}
会出现这样的报错:
探索真相
自己想了想是不是新页面的widget没有获取到上下文context,还用了ScopedModelDescendant包裹那个widget还是不行,又想了用context传参的方案,但是通过context还要一层层找到父控件的context的model,如果那么麻烦scopedmodel早就没人用了,所以都没解决这个问题。
以下是scopedmodel共享model的原理:
最后google了一番才找到了答案,别人跨页面也有这个问题,原文在这:github.com/brianegan/s…
而开发者是这样回答的:
1.用MaterialApp包裹组件,然后把model写在MaterialApp的上面
2.用CategoryEditPage包裹另一个页面
3.把这个model传给下一个页面
尝试第一种办法:在原来代码的基础上包裹一个MaterialApp
class ScopedPage extends StatelessWidget {
final CountModel _model = new CountModel();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text("scoped"),
),
body: Container(
child: new ScopedModel<CountModel>(
model: _model,
//child: CountWidget(),
//MaterialApp在这里!!
child: MaterialApp(
home: Column(
children: [
Text("不加ScopedModelDescendant父widget:"+_model.count.toString()),
ScopedModelDescendant<CountModel>(
builder: (context,child,model){
return Text("加了ScopedModelDescendant父widget:"+_model.count.toString());
}
),
CountWidget(),
CountWidget_2(),
],
),
)
),
));
}
}
终于成功了:
另外两种方法就不展示了。
总结
- ScopedModel是观察者模式的一个实现,原理就是把model放在父widget,model里的方法实现监听model数据的改变,如果发生改变,父widget就会通知子widget也改变。
- ScopedModel是支持全局状态管理的,但是要在第一个页面用MaterialApp或者每次跳转新页面也得把model穿过去,还或者对新页面用CategoryEditPage,操作起来对各个页面都有局限性。所以ScopedModel更适合用在同一个页面多个组件共享同一个model状态的局部状态管理。
- ScopedModel的局限性还体现在需要手动调用notifylistener(),如果model的状态复杂,得在处理事件逻辑的方法里反复调用notifylistener(),并且还要注意调用时机,并没有彻底体现数据源改变反映在UI上的自动性。所以scopedmodel更适合用在model数据状态不复杂的项目上。
- scolpedmodel中countmodel是要继承Model的,如果以后要换成别的状态管理模式,countmodel的代码就得重新调整,这也是这个模式的一个局限性。