在用Flutter进行界面开发时,我们经常会遇到数据传递的问题。但是由于Flutter采用树形结构,造成数据传递的链条有时候会很长,代码写起来也很不方便。
InheritedWidget可以让它的子节点能访问到它的公开属性,从而实现数据的跨Widget的传递。
InheritedWidget使用
我们先用一个Demo来看看InheritedWidget的使用方法。Demo如下,InheritedWidget子类InfoWidget的number
数值变化后,底下的三个InfoChildWidget显示的number
也会变化。
接下来我们来写代码。
- 由于InheritedWidget是抽象类,我们创建一个继承 自InheritedWidget的InfoWidget。
class InfoWidget extends InheritedWidget {
// 1
final int number;
// 2
InfoWidget({Key key, @required this.number, @required child})
: super(key: key, child: child);
//3
@override
bool updateShouldNotify(InfoWidget oldWidget) {
return number != oldWidget.number;
}
// 4
static InfoWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
}
代码说明:
-
number
就是定义的共享的数据; - InfoWidget的构造函数中,有三个参数除了
key
外,都是必传参数,number
是外部传入的给InfoWidget共享的数据,child
是子Widget;
- InheritedWidget是Widget的子类,但是没有StatefulWidget类似的State,这样InheritedWidget的所有属性都是不可变的,所以数据是需要父Widget提供的。
child
是InheritedWidget的必传参数,所以子类也得是必传参数。
- InheritedWidget的子类需要重写
updateShouldNotify
方法,这个方法如果返回true
,则会回调StatefulElement中state的didChangeDependencies
方法; -
of
这个静态方法是留给子Widget使用的,子Widget可以通过它获取到InheritedWidget的共享数据。
取
of
方法名是个约定俗成,当然也可以随便取个合法的方法名。
- 建一个Widget,它可以显示InfoWidget共享的数据
class InfoChildWidget extends StatelessWidget {
// 1
const InfoChildWidget();
@override
Widget build(BuildContext context) {
// 2
final int number = InfoWidget.of(context).number;
return Text("$number", style: TextStyle(color: Colors.amber, fontSize: 40));
}
}
- 使用InfoChildWidget的常量构造函数是为了解决不必要的重建和销毁。
-
InfoWidget.of(context)
就是上面提到的给子Widget使用的of
静态方法,然后取到number就可以直接显示了。
- 使用
InfoWidget(
number: _number,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InfoChildWidget(),
InfoChildWidget(),
InfoChildWidget(),
],
),
),
)
使用的时候是将InfoChildWidget做为InfoWidget的子Widget,我这里特意中间加了Center和Column,就是为了指出InfoChildWidget不一定需要是直接子Widget。
- 所有代码如下:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
int _number = 0;
void _incrementCounter() {
_number = Random().nextInt(100);
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: InfoWidget(
number: _number,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InfoChildWidget(),
InfoChildWidget(),
InfoChildWidget(),
],
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class InfoWidget extends InheritedWidget {
final int number;
InfoWidget({Key key, @required this.number, @required child})
: super(key: key, child: child);
@override
bool updateShouldNotify(InfoWidget oldWidget) {
return number != oldWidget.number;
}
static InfoWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
}
class InfoChildWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final int number = InfoWidget.of(context).number;
return Text("$number", style: TextStyle(color: Colors.amber, fontSize: 40));
}
}
效果如下:
InheritedWidget源码分析
设置inheritedWidgets
每个Element都有一个key为InheritedWidget类型,值为InheritedElement的Map属性_inheritedWidgets
。
Map? _inheritedWidgets;
每个Widget生成的Element挂载到Element Tree上的时候都会调用mount
方法:
void mount(Element? parent, dynamic newSlot) {
_updateInheritance();
}
mount
方法会调用_updateInheritance
方法:
void _updateInheritance() {
_inheritedWidgets = _parent?._inheritedWidgets;
}
如果不是InheritedElement,则_inheritedWidgets
都指向父Element的_inheritedWidgets
。
void _updateInheritance() {
final Map? incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap.from(incomingWidgets);
else
_inheritedWidgets = HashMap();
_inheritedWidgets![widget.runtimeType] = this;
}
如果是InheritedElement,先拷贝一份父节点的_inheritedWidgets
, 然后添加或者替换key为widget.runtimeType,值为InheritedElement的键值对。
注意:这里如果父类有相同的widget.runtimeType,则会被替换,也就是说如果有多个相同的InheritedWidget,子节点的Element只能找到离它最近的那个。
子Element对InheritedElement并添加依赖
我们来看看of
类方法调用的dependOnInheritedWidgetOfExactType
方法:
Set? _dependencies;
T? dependOnInheritedWidgetOfExactType({Object? aspect}) {
// 1
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
if (ancestor != null) {
// 2
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
_dependencies ??= HashSet();
// 3
_dependencies!.add(ancestor);
// 3
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
- 从
_inheritedWidgets
这个Map中找到类型对应的InheritedElement; - 如果找到则调用
dependOnInheritedElement
方法;dependOnInheritedElement
方法主要是将InheritedElement加入到_dependencies
这个Set中,然后InheritedElement调用updateDependencies
方法把子Element加入到_dependents
中。
void updateDependencies(Element dependent, Object? aspect) {
setDependencies(dependent, null);
}
void setDependencies(Element dependent, Object? value) {
_dependents[dependent] = value;
}
final Map _dependents = HashMap();
InheritedElement数据变化调用StatefulElement的didChangeDependencies
方法:
InheritedWidget调用build
方法的时候,会调用notifyClients
方法:
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
notifyClients
方法会对_dependents
中的每个子Element调用notifyDependent
方法,子Element会调用didChangeDependencies
方法:
void notifyClients(InheritedWidget oldWidget) {
for (final Element dependent in _dependents.keys) {
notifyDependent(oldWidget, dependent);
}
}
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
子Element调用didChangeDependencies
最后会重新构建:
void didChangeDependencies() {
markNeedsBuild();
}
当子Element为StateFulElement时,会将_didChangeDependencies
置为true;
void didChangeDependencies() {
super.didChangeDependencies();
_didChangeDependencies = true;
}
当重新构建时,StateFulElement会调用state的didChangeDependencies
方法。
void performRebuild() {
if (_didChangeDependencies) {
state.didChangeDependencies();
_didChangeDependencies = false;
}
super.performRebuild();
}
总结
InheritedWidget传递参数的方案只是把传参从Constructor变成了BuildContext。但是它还是有些的不完善的地方:
- 某个类型的InheritedWidget只能获取到最近的那一个;
- 重新构建没法只重构依赖InheritedWidget的子Widget,性能上不是太好。