flutter中InheritedWidget的介绍和运用
InheritedWidget 不继承自StatefulWidget,而是 InheritedWidget -> ProxyWidget -> Widget 这样的继承关系。简单来说,InheritedWidget 的作用是向它的子 Widget 有效地传播和分享数据,当 InheritedWidget 作为一个Parent Widget时,它下面的Widget tree的所有Widget都可以去和 InheritedWidget 发生数据传递和交互。当数据发生改变时,一部分控件需要 rebuild,另外的控件不需要 rebuild 的时候,可以使用 InheritedWidget,具体的介绍结合代码来看。
下面是 InheritedWidget 的一个实例:
class MyInheritedWidget extends InheritedWidget {
MyInheritedWidget({
Key key,
@required Widget child,
this.data,
}): super(key: key, child: child);
final data;
static MyInheritedWidget of(BuildContext context) {
return context.inheritFromWidgetOfExactType(MyInheritedWidget);
}
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) => data != oldWidget.data;
}
updateShouldNotify 是必须重写的一个方法,这个方法来决定什么时候需要去rebuild 控件
of 是一个类方法,返回了它自己,inheritFromWidgetOfExactType这个方法,我理解的是从父Widget中根据类型去找响应的Widget,但这个方法还有其他的作用,会在后面详细介绍。
初始化方法中,会传一个Child,并传递给super,也就是它的子Widget。
使用这个类:
class MyParentWidget... {
...
@override
Widget build(BuildContext context){
return new MyInheritedWidget(
data: counter,
child: new Row(
children: <Widget>[
...
],
),
);
}
}
子Widget怎么获取数据?
class MyChildWidget... {
...
@override
Widget build(BuildContext context){
final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context);
///
/// From this moment, the widget can use the data, exposed by the MyInheritedWidget
/// by calling: inheritedWidget.data
///
return new Container(
color: inheritedWidget.data.color,
);
}
}
应用场景:
- Widget A是一个按钮,点击的时候购物车数量加1.
- Widget B是一个显示购物车数量的文本.
- Widget C是显示一个固定的文本
- 在点击Widget A的时候,Widget B刷新数据,而不需要rebuild Widget C
代码如下:
class Item {
String reference;
Item(this.reference);
}
class _MyInherited extends InheritedWidget {
_MyInherited({
Key key,
@required Widget child,
@required this.data,
}) : super(key: key, child: child);
final MyInheritedWidgetState data;
@override
bool updateShouldNotify(_MyInherited oldWidget) {
return true;
}
}
class MyInheritedWidget extends StatefulWidget {
MyInheritedWidget({
Key key,
this.child,
}): super(key: key);
final Widget child;
@override
MyInheritedWidgetState createState() => new MyInheritedWidgetState();
static MyInheritedWidgetState of(BuildContext context){
return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
}
class MyInheritedWidgetState extends State<MyInheritedWidget>{
/// List of Items
List<Item> _items = <Item>[];
/// Getter (number of items)
int get itemsCount => _items.length;
/// Helper method to add an Item
void addItem(String reference){
setState((){
_items.add(new Item(reference));
});
}
@override
Widget build(BuildContext context){
return new _MyInherited(
data: this,
child: widget.child,
);
}
}
class MyTree extends StatefulWidget {
@override
_MyTreeState createState() => new _MyTreeState();
}
class _MyTreeState extends State<MyTree> {
@override
Widget build(BuildContext context) {
return new MyInheritedWidget(
child: new Scaffold(
appBar: new AppBar(
title: new Text('Title'),
),
body: new Column(
children: <Widget>[
new WidgetA(),
new Container(
child: new Row(
children: <Widget>[
new Icon(Icons.shopping_cart),
new WidgetB(),
new WidgetC(),
],
),
),
],
),
),
);
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
return new Container(
child: new RaisedButton(
child: new Text('Add Item'),
onPressed: () {
state.addItem('new item');
},
),
);
}
}
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
return new Text('${state.itemsCount}');
}
}
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Text('I am Widget C');
}
}
解释说明:
- 当点击Widget A的时候,_MyInherited 会被重新创建一个
- MyInheritedWidget 相当于是一个购物车,它的State通过static MyInheritedWidgetState of(BuildContext context)可以获取到.
- MyInheritedWidgetState 提供了公开的方法,getter (itemsCount)和addItem,这两个方法可以被子Widget调用.
- 每次我们添加一项到购物车时,MyInheritedWidgetState会rebuild
InheritedWidget是怎么去通知子Widget刷新数据的呢?
当一个子Widget调用MyInheritedWidget.of(context)时,会走下面的代码,把它自己的context传进来
static MyInheritedWidgetState of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
这个方法其实是做了两件事:
- 获取MyInheritedWidgetState里面的数据
- 会将调用该方法的Widget加入订阅者行列,当数据发生改变的时候,会通知这些Widget刷新数据,也就是rebuild.
工作的原理总结起来如下:
因为Widget A和Widget B订阅了InheritedWidget,所以点击Widget A的时候会发生:
- 触发了MyInheritedWidgetState的addItem的方法
- MyInheritedWidgetState的addItem的方法添加了新的一项数据到data中
- 触发了setState(),MyInheritedWidgetState rebuild
- 重新创建了一个 _MyInherited 对象,传入了新的数据,记录了新的State
- _MyInherited去查看是否需要通知订阅者,因为返回的是true,所以会通知订阅者
- Widget A和Widget B 被 rebuild了,Widget C没有被订阅,所以不会被rebuild
因为事实上Widget A是不需要被通知的,所以为了解决这个问题,需要修改代码
static MyInheritedWidgetState of([BuildContext context, bool rebuild = true]){
return (rebuild ? context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited
: context.ancestorWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
ancestorWidgetOfExactType 方法只会去获取Widget,不会发生订阅,所以Widget A就不会订阅了。
参考链接: https://www.didierboelens.com/2018/06/widget---state---context---inheritedwidget/