InheritedWidget
概述
在Flutter进行界面开发时,我们经常会遇到数据传递的问题。由于Flutter采用节点树的方式组织页面,以致于一个普通页面的节点层级会很深。此时,我们如果还是一层层传递数据,当需要修改数据时,就会比较麻烦。
此时,我们需要一种机制,能够让某一个节点下的所有子节点,访问该节点下的数据。
InheritedWidget就满足了我们这一需求。
使用方法
我们创建一个继承InheritedWidget
的Widget。
class MyInheritedWidget extends InheritedWidget {
final int data;
MyInheritedWidget(@required this.data, Widget child) : super(child: child);
static MyInheritedWidget getData(BuildContext context) {
return context.inheritFromWidgetOfExactType(MyInheritedWidget);
}
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
return oldWidget.data != data;
}
}
可以看到,我们提供了三个方法。
构造方法
InheritedWidget本身是一个没有界面的Widget,或者说是一个代理Widget,我们需要传入我们实际的Widget。
所以,在构造方法中,我们需要传入两个参数,一个是我们希望共享的数据(在本例中数据是int型,实际业务中共享的通常是一个相对复杂的数据),另一个就是我们带界面的Widget。
数据获取方法
static MyInheritedWidget getData(BuildContext context) {
return context.inheritFromWidgetOfExactType(MyInheritedWidget);
}
我们看一下inheritFromWidgetOfExactType
方法的源码:
/// Obtains the nearest widget of the given type, which must be the type of a
/// concrete [InheritedWidget] subclass, and registers this build context with
/// that widget such that when that widget changes (or a new widget of that
/// type is introduced, or the widget goes away), this build context is
/// rebuilt so that it can obtain new values from that widget.
/// The [aspect] parameter is only used when [targetType] is an
/// [InheritedWidget] subclasses that supports partial updates, like
/// [InheritedModel]. It specifies what "aspect" of the inherited
/// widget this context depends on.
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect });
注释说得很清楚,inheritFromWidgetOfExactType是获取指定context下的,最近的指定类型的Widget,并且此Widget必须是InheritedWidget的子类。
值得注意的是,它是一个静态方法。所以,只要是相同BuildContext
我们即可以在任意子节点上,通过这个静态方法,获取到我们的InheritedWidget
,然后读取或修改它的共享数据。
更新通知
这个方法,决定了是否通知子节点中StatefulWidget
的didChangeDependencies
方法是否调用。
/// Called when a dependency of this [State] object changes.
///
/// For example, if the previous call to [build] referenced an
/// [InheritedWidget] that later changed, the framework would call this
/// method to notify this object about the change.
///
/// This method is also called immediately after [initState]. It is safe to
/// call [BuildContext.inheritFromWidgetOfExactType] from this method.
///
/// Subclasses rarely override this method because the framework always
/// calls [build] after a dependency changes. Some subclasses do override
/// this method because they need to do some expensive work (e.g., network
/// fetches) when their dependencies change, and that work would be too
/// expensive to do for every build.
@protected
@mustCallSuper
void didChangeDependencies() { }
StatefulWidget的didChangeDependencies
方法就是与InheritedWidget
配合使用的。只有当InheritedWidget
发生更新并且决定通知时,didChangeDependencies
才会调用。
使用场景
通常,当我们需要在Widget树,由父节点向子节点传递数据时,会使用InheritedWidget
。一旦某个节点的Widget继承了InheritedWidget
,那么它的子节点,不论深度是多少,都可以获取到继承了InheritedWidget
的Widget,并取得其中的数据成员。
class TextWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return TextWidgetState();
}
}
class TextWidgetState extends State<TextWidget> {
@override
Widget build(BuildContext context) {
print("refresh text");
return Text(
"count : ${MyInheritedWidget.getData(context).data}"
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
//父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
//如果build中没有依赖InheritedWidget,则此回调不会被调用。
print("Dependencies change");
}
}
所以,InheritedWidget
适合做某个子树的数据管理Widget。当某个页面、模块或控件依赖一个或一组数据时。我们可以在其上层,创建一个继承了InheritedWidget
的数据管理Widget。
通过这样的方式,至少我们不用对数据进行层层传递。