数据传递/状态管理 一InheritedWidget
InheritedWidget是一个特殊的widget,可以存储和获取数据,子组件可以获取到存储的数据,常用的 MediaQuery
、Theme
就是继承了 InheritedWidget
。先通过MediaQuery来学习InheritedWidget。
- MediaQuery
class MediaQuery extends InheritedWidget {
const MediaQuery({
Key key,
@required this.data,
@required Widget child,
}) : assert(child != null),
assert(data != null),
super(key: key, child: child);
///...
final MediaQueryData data;
///...
static MediaQueryData of(BuildContext context, { bool nullOk = false }) {
///...
}
}
通过 MediaQuery.of(context)
获取到MediaQueryData
对象,然后获取到设备屏幕相关的信息。
///获取屏幕宽度
MediaQuery.of(context).size.width;
代码查找,下面这些地方创建了MediaQuery
,
在app.dart里面可以发现 WidgetsApp
如下代码,build 方法返回一个DefaultFocusTraversal
组件,而DefaultFocusTraversal
组件的 child
就是 MediaQuery
。
@override
Widget build(BuildContext context) {
///...
return DefaultFocusTraversal(
policy: ReadingOrderTraversalPolicy(),
child: MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window),
child: Localizations(
locale: appLocale,
delegates: _localizationsDelegates.toList(),
child: title,
),
),
);
}
继续通过代码查找在 MaterialApp
的源码里面找到了 WidgetsApp
,在build
方法里面返回了ScrollConfiguration
类,ScrollConfiguration
的child
就是WidgetsApp
。
@override
Widget build(BuildContext context) {
Widget result = WidgetsApp(
key: GlobalObjectKey(this),
navigatorKey: widget.navigatorKey,
navigatorObservers: _navigatorObservers,
pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) =>
MaterialPageRoute<T>(settings: settings, builder: builder),
home: widget.home,
///...
);
return ScrollConfiguration(
behavior: _MaterialScrollBehavior(),
child: result,
);
}
通过这些代码,可以推断出 MaterialApp
的home
是属于MediaQuery
的子组件,所有home下面所有组件都能够通过 MediaQuery.of(context)
来获取其共享的数据。但是注意并不是任何组件的任何地方,只有当前组件执行完 didChangeDependencies
方法之后才能正确的获取到 InheritedWidget
对象的数据。
-
示例
完成下面的示例,页面进来加载用户信息,保存和展示,然后在另外的页面修改编辑后返回,展示的数据也改变了:
完成上面的示例首先创建一个数据实体类,
///共享的数据
class InheritedWidgetData {
String userName;
int userAge;
InheritedWidgetData({this.userName = "", this.userAge = 0});
void setUser(String userName, int userAge) {
this.userName = userName;
this.userAge = userAge;
}
}
InheritedWidgetDemo
继承 InheritedWidget
,data
存放着用户的数据。提供一个of
的静态方法供子组件调用,复写updateShouldNotify
方法判断是否需要更新。
///主Widget 继承InheritedWidget 存放数据
class InheritedWidgetDemo extends InheritedWidget {
final InheritedWidgetData data;
InheritedWidgetDemo({@required this.data, @required Widget child})
: super(child: child);
@override
bool updateShouldNotify(InheritedWidgetDemo oldWidget) {
return data != oldWidget.data;
}
static InheritedWidgetData of(BuildContext context) {
final InheritedWidgetDemo user =
context.inheritFromWidgetOfExactType(InheritedWidgetDemo);
return user.data;
}
}
数据展示Widget,在 didChangeDependencies
方法里面获取用户数据。页面初始化开启模拟网络请求等待3秒后,调用 setUser
方法修改 InheritedWidgetDemo
中的数据,然后重建界面。当用户信息为空的话页面显示加载等待组件。
class CenterWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() => _CenterWidgetState();
}
class _CenterWidgetState extends State<CenterWidget> {
InheritedWidgetData _userData;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_userData = InheritedWidgetDemo.of(context);
_getUserData();
}
_getUserData() async {
///模拟网络请求 等待五秒
await Future.delayed(Duration(seconds: 3));
InheritedWidgetDemo.of(context).setUser("李华", 18);
///setState 触发页面刷新
setState(() {});
}
@override
Widget build(BuildContext context) {
return new Center(
/// 数据为空显示加载圆圈
child: _userData.userName == ""
? CircularProgressIndicator()
: new UserInfoWidget(),
);
}
}
用户信息展示Widget,和上面的Widget一样,在 didChangeDependencies
方法里面获取用户数据并且展示。
///用户信息Widget
class UserInfoWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() => _UserInfoState();
}
class _UserInfoState extends State<UserInfoWidget> {
InheritedWidgetData _userData;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_userData = InheritedWidgetDemo.of(context);
}
@override
Widget build(BuildContext context) {
return new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Text(_userData.userName),
new Text(_userData.userAge.toString())
],
),
);
}
}
用户资料修改界面,和上面一样在 didChangeDependencies
方法里面拿到数据,点击保存按钮的时候 InheritedWidgetDemo.of(context).setUser(_userName, userAge)
修改数据,当返回展示界面的时候,界面会拿到新数据重建。
class EditUserPageWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("修改数据"),
),
body: new EditUserWidget(),
);
}
}
class EditUserWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() => _EditUserState();
}
class _EditUserState extends State<EditUserWidget> {
InheritedWidgetData _userData;
String _userName;
String _userAge;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_userData = InheritedWidgetDemo.of(context);
_userName = _userData.userName;
if (_userData.userAge > 0) {
_userAge = _userData.userAge.toString();
} else {
_userAge = "";
}
}
@override
Widget build(BuildContext context) {
return new Center(
child: new Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new TextField(
///文本内容控制器
controller: TextEditingController.fromValue(new TextEditingValue(
text: _userName,
///光标移动到最后面
selection: TextSelection.fromPosition(new TextPosition(
affinity: TextAffinity.downstream, offset: _userName.length)),
)),
decoration: new InputDecoration(labelText: "输入姓名"),
///内容变化监听
onChanged: (value) {
_userName = value;
},
),
new TextField(
///文本内容控制器
controller: TextEditingController.fromValue(new TextEditingValue(
text: _userAge,
///光标移动到最后面
selection: TextSelection.fromPosition(new TextPosition(
affinity: TextAffinity.downstream, offset: _userAge.length)),
)),
decoration: new InputDecoration(labelText: "输入年龄"),
///内容变化监听
onChanged: (value) {
_userAge = value;
},
),
new FlatButton(
onPressed: () {
int userAge;
try {
///保存当前修改的值
userAge = int.parse(_userAge);
InheritedWidgetDemo.of(context).setUser(_userName, userAge);
///关闭当前界面
Navigator.pop(context);
} catch (e) {}
},
child: new Text("保存"))
],
),
);
}
}
由于用户数据需要在全局共享,所以 InheritedWidgetDemo
要在顶层入口MaterialApp
之上,当然在日常开发并不是所有的数据都是全局保存的,每个子页面都会管理自己所属的数据,只需要把 InheritedWidget
放在子页面的最外层。
class AppPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new InheritedWidgetDemo(
data: new InheritedWidgetData(),
child: new MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: new Scaffold(
appBar: new AppBar(
title: new Text("数据传递"),
),
floatingActionButton: new Builder(builder: (context) {
return new FloatingActionButton(
child: new Icon(Icons.edit),
onPressed: () {
///push到编辑页面
Navigator.of(context).push(
new MaterialPageRoute(builder: (BuildContext context) {
return EditUserPageWidget();
}));
});
}),
body: new CenterWidget(),
),
));
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
///使用
return AppPage();
}
}
-
关于
didChangeDependencies
的回调,在生命周期提到过,解释是在State
对象的依赖发生变化时会被调用,结合InheritedWidget
看下面这个demo。两个Text,一个依赖了
InheritedWidget
,一个没有。点击按钮修改InheritedWidget
的值,上面的text会根据值的变化刷新重建,并回调了didChangeDependencies
,下面的text由于没有数据依赖,所以只有初始化的时候回调了一次didChangeDependencies
class InheritedRely extends StatefulWidget { @override State<StatefulWidget> createState() { return new _InheritedRelyState(); } } class _InheritedRelyState extends State<InheritedRely> { int _state = 0; @override Widget build(BuildContext context) { return new InheritedTestWidget( data: _state, child: new MaterialApp( title: "Inherited数据依赖", home: new Scaffold( body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new TextWidget(), new Text1Widget() ], ), ), floatingActionButton: new FloatingActionButton( onPressed: () { setState(() { ///修改state _state++; }); }, child: new Icon(Icons.add), ), ), )); } } class Text1Widget extends StatefulWidget { @override State<StatefulWidget> createState() { return new _Text1WidgetState(); } } ///这个text没有使用InheritedWidget的数据 class _Text1WidgetState extends State<Text1Widget> { @override Widget build(BuildContext context) { return Text("这个text没有使用InheritedWidget的数据"); } @override void didChangeDependencies() { super.didChangeDependencies(); print("_Text1------>>>>>>didChangeDependencies"); } } ///这个text使用了InheritedWidget的数据 class TextWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _TextWidgetState(); } } class _TextWidgetState extends State<TextWidget> { @override Widget build(BuildContext context) { ///InheritedTestWidget.of(context) 依赖了InheritedWidget的数据 return Text("这个text使用了InheritedWidget的数据" + InheritedTestWidget.of(context).toString()); } @override void didChangeDependencies() { super.didChangeDependencies(); print("_Text------>>>>>>didChangeDependencies"); } } class InheritedTestWidget extends InheritedWidget { final int data; InheritedTestWidget({@required this.data, @required Widget child}) : super(child: child); @override bool updateShouldNotify(InheritedTestWidget oldWidget) { return data != oldWidget.data; } static int of(BuildContext context) { final InheritedTestWidget user = context.inheritFromWidgetOfExactType(InheritedTestWidget); return user.data; } }
代码和演示清楚的解释了,当
InheritedWidget
的State
对象发生变化时,它下面子组件只要是对InheritedWidget
有依赖的都会调didChangeDependencies
和build
进行刷新重建。 -
scoped_model
上面的例子展示了如何使用
InheritedWidget
,有个问题就是在数据变化之后每次都需要手动调动setState
方法才会进行重建。如何让组件自动刷新不需要我们自己手动调用setState
方法呢。这需要使用到
scoped_model
库 ,scoped_model
是基于InheritedWidget
实现的,源码稍后分析,先通过刚刚用户资料编辑的demo来学习如何使用这个库,展示效果和上面的demo一样。因为是第三方库第一步肯定是添加依赖
scoped_model: ^1.0.1
。文档在这里(需要梯子)。用户model,继承
Model
提供一个of
方法供组件调用获取model,注意rebuildOnChange
参数是可选的,默认为fales,为false的时候获取的model数据变化之后不会刷新界面。class UserMode extends Model { String userName; int userAge; UserMode({this.userName, this.userAge}); void setUserInfo(String userName, int userAge) { this.userName = userName; this.userAge = userAge; ///修改完数据 调用刷新方法 notifyListeners(); } ///rebuildOnChange 此方法拿到的model对象修改后是否需要刷新 static UserMode of(BuildContext context) => ScopedModel.of<UserMode>(context, rebuildOnChange: true); }
展示组件,逻辑基本和
InheritedWidget
的一样,初始化的时候模拟请求,延迟3秒修改数据,当没有数据的时候加载等待小圆圈,不同的是获取数据的方法,这里变成通过model的of
方法。class CenterWidget extends StatefulWidget { @override State<StatefulWidget> createState() => _CenterWidgetState(); } class _CenterWidgetState extends State<CenterWidget> { @override void initState() { super.initState(); _getUserData(); } _getUserData() async { ///模拟网络请求 等待五秒 await Future.delayed(Duration(seconds: 3)); UserMode.of(context).setUserInfo("李华", 22); } @override Widget build(BuildContext context) { return new Center( /// 数据为空显示加载圆圈 child: UserMode.of(context).userName == null ? CircularProgressIndicator() : new UserInfoWidget()); } }
除了
ScopedModel.of
方法还可以通过ScopedModelDescendant
来获取,在需要获取数据的组件外加一层ScopedModelDescendant
,用户信息使用此方法。///用户信息Widget class UserInfoWidget extends StatefulWidget { @override State<StatefulWidget> createState() => _UserInfoState(); } class _UserInfoState extends State<UserInfoWidget> { @override Widget build(BuildContext context) { return new ScopedModelDescendant<UserMode>(builder: (context, child, model) { return new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: <Widget>[ ///可以直接通过mode拿到数据 new Text(model.userName), new Text(model.userAge.toString()) ], ), ); }); } }
信息编辑页面逻辑也一样,获取和修改数据的方式不一样。
class EditUserPageWidget extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("修改数据"), ), body: new EditUserWidget(), ); } } class EditUserWidget extends StatefulWidget { @override State<StatefulWidget> createState() => _EditUserState(); } class _EditUserState extends State<EditUserWidget> { UserMode _userMode; String _userName; String _userAge; @override Widget build(BuildContext context) { _userMode = UserMode.of(context); _userName = _userMode.userName; _userAge = _userMode.userAge.toString(); return new Center( child: new Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new TextField( ///文本内容控制器 controller: TextEditingController.fromValue(new TextEditingValue( text: _userName, ///光标移动到最后面 selection: TextSelection.fromPosition(new TextPosition( affinity: TextAffinity.downstream, offset: _userName.length)), )), decoration: new InputDecoration(labelText: "输入姓名"), ///内容变化监听 onChanged: (value) { _userName = value; }, ), new TextField( ///文本内容控制器 controller: TextEditingController.fromValue(new TextEditingValue( text: _userAge, selection: TextSelection.fromPosition(new TextPosition( affinity: TextAffinity.downstream, offset: _userAge.length)), )), decoration: new InputDecoration(labelText: "输入年龄"), ///内容变化监听 onChanged: (value) { _userAge = value; }, ), new FlatButton( onPressed: () { int userAge; try { ///保存当前修改的值 userAge = int.parse(_userAge); _userMode.setUserInfo(_userName, userAge); ///关闭当前界面 Navigator.pop(context); } catch (e) {} }, child: new Text("保存")) ], ), ); } }
主Widget,和
InheritedWidget
一样需要在最外层,使用ScopedModel
。class ScopedModelDemo extends StatefulWidget { @override State<StatefulWidget> createState() { return new _ScopedModelDemoState(); } } class _ScopedModelDemoState extends State<ScopedModelDemo> { @override Widget build(BuildContext context) { return new ScopedModel<UserMode>( model: UserMode(), child: new MaterialApp( theme: ThemeData( primarySwatch: Colors.blue, ), home: new Scaffold( appBar: new AppBar( title: new Text("ScopedModelDemo"), ), floatingActionButton: new Builder(builder: (context) { return new FloatingActionButton( child: new Icon(Icons.edit), onPressed: () { ///push到编辑页面 Navigator.of(context).push( new MaterialPageRoute(builder: (BuildContext context) { return EditUserPageWidget(); })); }); }), body: new CenterWidget(), ), )); } }
效果和上面一样,不上图了。代码中没有任何地方使用到
setState
数据更新之后,界面自动刷新重建。上面说到了这个库也是基于InheritedWidget
,具体如何实现的,通过源码来分析。首先看下Model的源码,
abstract class Model extends Listenable { final Set<VoidCallback> _listeners = Set<VoidCallback>(); int _version = 0; int _microtaskVersion = 0; /// [listener] will be invoked when the model changes. @override void addListener(VoidCallback listener) { _listeners.add(listener); } /// [listener] will no longer be invoked when the model changes. @override void removeListener(VoidCallback listener) { _listeners.remove(listener); } /// Returns the number of listeners listening to this model. int get listenerCount => _listeners.length; /// Should be called only by [Model] when the model has changed. @protected void notifyListeners() { // We schedule a microtask to debounce multiple changes that can occur // all at once. if (_microtaskVersion == _version) { _microtaskVersion++; scheduleMicrotask(() { _version++; _microtaskVersion = _version; // Convert the Set to a List before executing each listener. This // prevents errors that can arise if a listener removes itself during // invocation! _listeners.toList().forEach((VoidCallback listener) => listener()); }); } } }
Model继承至
Listenable
,维护着一个VoidCallback
类型的集合,/// Signature of callbacks that have no arguments and return no data. typedef VoidCallback = void Function();
VoidCallback
就是一个没有返回值的方法,notifyListeners
方法里面遍历调用了所有集合里面的方法。再看入口
ScopedModel
,class ScopedModel<T extends Model> extends StatelessWidget { /// The [Model] to provide to [child] and its descendants. final T model; /// The [Widget] the [model] will be available to. final Widget child; ScopedModel({@required this.model, @required this.child}) : assert(model != null), assert(child != null); @override Widget build(BuildContext context) { return AnimatedBuilder( animation: model, builder: (context, _) => _InheritedModel<T>(model: model, child: child), ); } static T of<T extends Model>( BuildContext context, { bool rebuildOnChange = false, }) { final Type type = _type<_InheritedModel<T>>(); Widget widget = rebuildOnChange ? context.inheritFromWidgetOfExactType(type) : context.ancestorWidgetOfExactType(type); if (widget == null) { throw new ScopedModelError(); } else { return (widget as _InheritedModel<T>).model; } } }
代码很简单
of
方法为Model方法提供获取值的方法,而且是通过inheritFromWidgetOfExactType
和上面InheritedWidget
的of
差不多,这里也可以解释了上面Model里面为什么rebuildOnChange
为fals的时候不会更新界面的原因。然后查看
bulid
方法返回的是一个AnimatedBuilder
,两个参数,model和_InheritedModel
,查看_InheritedModel
,果然,继承了InheritedWidget
。并且存放了model和一个int类型的version
通过version来判断是否更新。class _InheritedModel<T extends Model> extends InheritedWidget { final T model; final int version; _InheritedModel({Key key, Widget child, T model}) : this.model = model, this.version = model._version, super(key: key, child: child); @override bool updateShouldNotify(_InheritedModel<T> oldWidget) => (oldWidget.version != version); }
回到
AnimatedBuilder
查看它的代码class AnimatedBuilder extends AnimatedWidget { /// Creates an animated builder. /// /// The [animation] and [builder] arguments must not be null. const AnimatedBuilder({ Key key, @required Listenable animation, @required this.builder, this.child, }) : assert(animation != null), assert(builder != null), super(key: key, listenable: animation); /// Called every time the animation changes value. final TransitionBuilder builder; final Widget child; @override Widget build(BuildContext context) { return builder(context, child); } }
看不出有什么重要的代码,点击
super
往上走看AnimatedWidget
的代码,abstract class AnimatedWidget extends StatefulWidget { ///... } class _AnimatedState extends State<AnimatedWidget> { @override void initState() { super.initState(); widget.listenable.addListener(_handleChange); } @override void didUpdateWidget(AnimatedWidget oldWidget) { super.didUpdateWidget(oldWidget); if (widget.listenable != oldWidget.listenable) { oldWidget.listenable.removeListener(_handleChange); widget.listenable.addListener(_handleChange); } } @override void dispose() { widget.listenable.removeListener(_handleChange); super.dispose(); } void _handleChange() { setState(() { // The listenable's state is our build state, and it changed already. }); } @override Widget build(BuildContext context) => widget.build(context); }
看到这里就很清晰了,通过观察者模式,
_InheritedModel
里的数据也就是Model是被观察者,在_AnimatedState
的initState
和dispose
方法来 订阅和取消,Model发生变化时,去调用notifyListeners
来通知所有观察者执行setState
刷新重构造界面。还有一个
ScopedModelDescendant
,class ScopedModelDescendant<T extends Model> extends StatelessWidget { /// Called whenever the [Model] changes. final ScopedModelDescendantBuilder<T> builder; /// An optional constant child that does not depend on the model. This will /// be passed as the child of [builder]. final Widget child; /// An optional constant that determines whether the final bool rebuildOnChange; /// Constructor. ScopedModelDescendant({ @required this.builder, this.child, this.rebuildOnChange = true, }); @override Widget build(BuildContext context) { return builder( context, child, ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange), ); } }
本质也是ScopedModel.of
方法拿到数据的。
初学小白的个人学习笔记,欢迎大佬检查,如有不对请指出。