InheritedWidget 初识
InheritedWidget是Flutter中非常重要的一个功能型Widget,它可以高效的将数据在Widget树中向下传递、共享,这在一些需要在Widget树中共享数据的场景中非常方便,如Flutter中,正是通过InheritedWidget来共享应用主题(Theme)和Locale(当前语言环境)信息的。
InheritedWidget和React中的context功能类似,和逐级传递数据相比,它们能实现组件跨级传递数据。InheritedWidget的在Widget树中数据传递方向是从上到下的,这和Notification的传递方向正好相反。
可以高效的在widget tree中传递信息的widgets基类。
可以使用BuildContext.inheritFromWidgetOfExactType
获取指定类型的 inherited widget 的"最近"的实例。
此处的"最近"的意思是从当前widget(调用BuildContext.inheritFromWidgetOfExactType
的widget)向上溯源,找到第一个指定类型的inherited widget的位置。
当使用这种方式引用Inherited widgets时,inherited widget 变化状态时 会通知引用者rebuild。
样例代码:
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
@required this.color,
@required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.inheritFromWidgetOfExactType(FrogColor) as FrogColor;
}
@override
bool updateShouldNotify(FrogColor old) => color != old.color;
}
为InheritedWidget提供了静态方法 of
,用以调用BuildContext.inheritFromWidgetOfExactType
。
这允许类去定义自己的回溯逻辑 如果在scope内为发现widget。在上述的例子中,这种情况下会返回null,但是也可以定义一个默认值。
某些时候,of
方法返回的是数据而不是 inherited widget,比如 在这个例子中可以返回一个Color
来替代FrogColor
widget。
偶尔,inherited widget也会是另一个类的具体实现,是private的, 这种情况下of
方法需要放在public class上。比如, Theme
就是实现了StatelessWidget来构建
一个私有的inherited widget。Theme.of
使用BuildContext.inheritFromWidgetOfExactType
来查找inherited widget 并返回 ThemeData
。
class Theme extends StatelessWidget {
...
static ThemeData of(BuildContext context, { bool shadowThemeOnly = false }) {
final _InheritedTheme inheritedTheme = context.inheritFromWidgetOfExactType(_InheritedTheme);
if (shadowThemeOnly) {
if (inheritedTheme == null || inheritedTheme.theme.isMaterialAppTheme)
return null;
return inheritedTheme.theme.data;
}
...
}
}
class _InheritedTheme extends InheritedWidget {
...
}
didChangeDependencies
继承StatefulWidget时State对象有一个回调didChangeDependencies,它会在“依赖”发生变化时被Flutter Framework调用。
而这个“依赖”指的就是是否使用了父widget中InheritedWidget的数据,如果使用了,则代表有依赖,如果没有使用则代表没有依赖。
这种机制可以使子组件在所依赖的主题、locale等发生变化时有机会来做一些事情。
接下来我们看一下InheritedWidget版本的计数器。
首先,我们通过继承InheritedWidget,将当前计数器点击次数保存在ShareDataWidget的data属性中:
class ShareDataWidget extends InheritedWidget {
ShareDataWidget({
@required this.data,
Widget child
}) :super(child: child);
final int data; //需要在子树中共享的数据,保存点击次数
//定义一个便捷方法,方便子树中的widget获取共享数据
static ShareDataWidget of(BuildContext context) {
return context.inheritFromWidgetOfExactType(ShareDataWidget);
}
//该回调决定当data发生变化时,是否通知子树中依赖data的Widget
@override
bool updateShouldNotify(ShareDataWidget old) {
//如果返回true,则子树中依赖(build函数中有调用)本widget
//的子widget的`state.didChangeDependencies`会被调用
return old.data != data;
}
}
然后我们实现一个子widget _TestWidget,在其build方法中引用ShareDataWidget中的数据;同时,在其didChangeDependencies() 回调中打印日志:
class __TestWidgetState extends State<_TestWidget> {
@override
Widget build(BuildContext context) {
//使用InheritedWidget中的共享数据
return Text(ShareDataWidget
.of(context)
.data
.toString());
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
//父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
//如果build中没有依赖InheritedWidget,则此回调不会被调用。
print("Dependencies change");
}
}
最后,我们创建一个按钮,每点击一次,就将ShareDataWidget的值自增:
class _InheritedWidgetTestState extends State<InheritedWidgetTest> {
int count = 0;
@override
Widget build(BuildContext context) {
return Center(
child: ShareDataWidget( //使用ShareDataWidget
data: count,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: _TestWidget(),//子widget中依赖ShareDataWidget
),
RaisedButton(
child: Text("Increment"),
//每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新
onPressed: () => setState(() => ++count),
)
],
),
),
);
}
}
运行后界面如下:
[图片上传失败...(image-689242-1563202790002)]
每点击一次按钮,计数器就会自增,控制台就会打印一句日志:
I/flutter (16850): Dependencies change
可见依赖发生变化后,其didChangeDependencies()会被调用。
但是,如果_TestWidget的build方法中没有使用ShareDataWidget的数据,那么它的didChangeDependencies()将不会被调用,因为它并没有依赖ShareDataWidget。
Flutter framework是怎么知道子widget有没有依赖InheritedWidget的?
下一篇文章会从源码的角度分析并解释这个问题,敬请期待。
如果你觉得这篇文章对你有益,还请帮忙转发和点赞,万分感谢。
[图片上传失败...(image-630a2a-1563202790002)]
网友评论