除了StatefulWidget、StatelessWidget之外flutter还提供了另外一个用的Widget组件即InheritedWidget。那么该组件的作用时什么呢?
下面我们定义一个嵌套三层的数据传递例子:
class DataTransferAWidget extends StatelessWidget {
final int data;
DataTransferAWidget(this.data);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(child: Text("A:${data.toString()}")),
Expanded(
//子组件依赖自己的data数据
child: DataTransferBWidget(data),
)
],
),
);
}
}
class DataTransferBWidget extends StatelessWidget {
final int data;
DataTransferBWidget(this.data);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(child: Text("B:${data.toString()}")),
//子组件依赖自己的data数据
Expanded(child: DataTransferCWidget(data))
]));
}
}
class DataTransferCWidget extends StatelessWidget {
final int data;
DataTransferCWidget(this.data);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center, child: Text("C:${data.toString()}"));
}
}
class DataTransferDemoPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _DataTransferDemoPageState();
}
}
class _DataTransferDemoPageState extends State<DataTransferDemoPage> {
//定义一个待向子widget传递的数据
int data = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("数据传递"),
),
body: Container(
alignment: Alignment.center,
//向子传递data
child: DataTransferAWidget(data)),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
data++;
});
}),
);
}
上面例子你看到,每个DataTransferWidget
的构造函数都依赖父widget的data,如果还有第4层,第5层...等嵌套的话,data要不停的通过构造函数传递,甚是麻烦。
既然每层widget依赖的都是根的data,那么为什么不定义一个全局静态变量来做呢?(好想法,我们试一下)
typedef ChildWidgetBuilder =DataTransferCWidget Function(int);
class DataTransferAWidget extends StatelessWidget {
//下面代码已经没有意义了
//final int data;
//DataTransferAWidget(this.data);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(child: Text("A:${DataModel.data.toString()}")),
Expanded(
child: DataTransferBWidget(),
)
],
),
);
}
}
class DataTransferBWidget extends StatelessWidget {
//下面代码已经没有意义了
//final int data;
//DataTransferBWidget(this.data);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(child: Text("B:${DataModel.data.toString()}")),
Expanded(child: DataTransferCWidget())
]));
}
}
class DataTransferCWidget extends StatelessWidget {
//下面代码已经没有意义了
//final int data;
//DataTransferCWidget(this.data);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center, child: Text("C:${DataModel.data.toString()}"));
}
}
//静态变量
class DataModel{
static int data=0;
}
class DataTransferDemoPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _DataTransferDemoPageState();
}
}
class _DataTransferDemoPageState extends State<DataTransferDemoPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("数据传递"),
),
body: Container(
alignment: Alignment.center, child: DataTransferAWidget()),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
DataModel.data++;
});
}),
);
}
}
全局static类型的变量是不是更方便一些呢?!
flutter官方api是这样说的:有效地在树中传播信息的小部件的基类,下面咱们来看一下它的定义:
//我们可以看到该类是一个抽象类
abstract class InheritedWidget {
//这是一个常量构造函数,常量的作用就是一旦编译后就不可能再被修改
const InheritedWidget({ Key key, Widget child })
: super(key: key, child: child);
//这个函数不在我们的讨论范围之内
@override
InheritedElement createElement() => InheritedElement(this);
//这个函数可以接收旧的对象,用来判断新旧InheritedWidget是否一样,它又什么作用呢?往下看...
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
它是一个抽象类,这就意味着我们要去实现它:
class TestModel {
int model = 0;
void add() {
model++;
}
}
class MyInheriteWidget extends InheritedWidget {
//这里我们定义一个我们准备向后代传递的数据
final DataModel model;
//另外InheriteWidget需要包裹一个Widget
//由于InheritedWidget仅用来传输数据,
//所以这个child或者child的后代需要是一个StatelessWidget或StatefulWidget
final Widget child;
MyInheriteWidget({Key key, @required this.model, @required this.child})
: super(key: key, child: child);
//这里我们要判断新旧widget是否相同
@override
bool updateShouldNotify(MyInheriteWidget oldWidget) {
return model != oldWidget.model;
}
static TestModel of(BuildContext context, [bool onChangeBuild = true]) {
var widget = onChangeBuild
? context.inheritFromWidgetOfExactType(MyInheriteWidget)
: context
.ancestorInheritedElementForWidgetOfExactType(MyInheriteWidget)
.widget;
return (widget as MyInheriteWidget).model;
}
}
此时,一个可以用与向后台传递数据的InheritedWidget就完成了,下面来看如何使用:
class _InheriteWidgetDemoPage extends State<InheriteWidgetDemoPage> {
//在一个有状态的widget中定义它需要维护的状态
var testModel = TestModel();
@override
Widget build(BuildContext context) {
//使用InheritedWidget向后代传递数据
return MyInheriteWidget(
model: testModel,
child: Scaffold(
appBar: AppBar(title: Text("title")),
//或许你想这样使用,这是错误的,我们应该获取的是InheritedWidget的数据而不是当前widget的
//body:Text(testModel.model.toString())
//为了更清楚,我们将获取InheritedWidget的数据放在一个单独的widget中执行,如下
body: TestAWidget(),
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
MyInheriteWidget.of(context).add();
});
},
),
)),
);
}
}
class TestAWidget extends StatelessWidget {
TestAWidget();
@override
Widget build(BuildContext context) {
//官方文档提到我们应该这样获取InheritedWidget上传递的数据,是不是有点类似全局静态变量呢?!
//不同的是 这是从context获取的,因为context可以贯穿整个widget依赖树,像android的context
//官方给的demo不是下面这样
//var myInheritedWidget = context.inheritFromWidgetOfExactType(MyInheriteWidget) as MyInheriteWidget;
//return Center(child: Text(myInheritedWidget.model.model.toString()));
//应该是这样,将获取传递对象的代码放在MyInheritedWidget中,这仅是为了代码可读性
var model = MyInheriteWidget.of(context).model.toString();
return Center(child: Text(model));
}
}
此时InheritedWidget向后代传递数据的方式已经完了,从现有的实现来说并没有比全局静态变量有什么优略之处,如果仅是这样也就没有它存在的价值了,请往下看...
要弄清楚updateShouldNotify的作用我们还要翻一翻源码了,不然只看api文档还是一头雾水,上源码:
//我们知道一个widget由:widget,element,ReanderObject三部分组成
class InheritedElement extends ProxyElement {
...
@override
void updated(InheritedWidget oldWidget) {
//通过字符串搜索你会找到该方法是在此处调用的
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
@override
void notifyClients(InheritedWidget oldWidget) {
...
notifyDependent(oldWidget, dependent);
}
}
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
...
}
class StatefulElement extends ComponentElement {
@override
void didChangeDependencies() {
super.didChangeDependencies();
//调了这么一圈最后是为了调用didChangeDependencies方法
_state.didChangeDependencies();
}
}
abstract class ProxyElement extends ComponentElement{
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
}
从上面重要的源码可以看到最后updateShouldNotify决定了didChangeDependencie()会不会被调用。
另外,分析源码的调用过程,也可以借助idea的debugger模式,在Frames里面我们可以看到代码是如何一步步被调用的。
didChangeDependencie是State中定义的一个回调函数,而State正是暴漏StatefulWidget生命周期的地方,我们可以同步实现State的不同方法,来对widget的生命周期变化时做出一些响应。
上面例子我们定义了一个无状态的TestAWidget来演示如果获取InheritedWidget要向子传递的数据,下面我们通过一个有状态的控件来展示在获取数据的同时响应didChangeDependencie的调用。
lass TestBWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _TestBWidgetState();
}
}
class _TestBWidgetState extends State<TestBWidget> {
//这里就是要被调用的地方
@override
void didChangeDependencies() {
super.didChangeDependencies();
print('TestBWidget');
}
@override
Widget build(BuildContext context) {
var model = MyInheriteWidget.of(context).model.toString();
return Center(child: Text(model));
}
}
现在我们已经知道了updateShouldNotify返回true时didChangeDependencies()就会被调用。
InheritedWidget是为了向后代传递数据,如果InheritedWidget发生了嵌套呢?可能你只想响应某一个先辈的didChangeDependencies()调用,这也是可以,通过ancestorInheritedElementForWidgetOfExactType来获取先辈的数据,不会将自己的didChangeDependencies注册进该先辈的调用集合。
前面的代码中有一段是这样的:
static TestModel of(BuildContext context, [bool onChangeBuild = true]) {
//通过一个变量来控制调用哪个方法获取先辈的数据
var widget = onChangeBuild
? context.inheritFromWidgetOfExactType(MyInheriteWidget)
: context
.ancestorInheritedElementForWidgetOfExactType(MyInheriteWidget)
.widget;
return (widget as MyInheriteWidget).model;
}
要搞清楚inheritFromWidgetOfExactType与ancestorInheritedElementForWidgetOfExactType的区别,我认为看源代码是最直接的:
...
@override
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
//重点再这里
if (ancestor != null) {
return inheritFromElement(ancestor, aspect: aspect);
}
_hadUnsatisfiedDependencies = true;
return null;
}
@override
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
return ancestor;
}
@override
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
_dependencies ??= HashSet<InheritedElement>();
//将widget添加到依赖集合后就会收到didChangeDependencies调用
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
...
下面配上一张流程图来加深一下印象:
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有