往期回顾
从零开始的 Flutter 之旅: StatelessWidget
从零开始的 Flutter 之旅: StatefulWidget
在之前的文章中,介绍了 StatelessWidget 与 StatefulWidget 的特性与它们的呈现原理。
这期要聊的是它们的另一个兄弟 InheritedWidget。
特性
InheritedWidget 是 Flutter 中的一个非常重要的功能组件,它能够提供数据在 widget 树中从上到下进行传递。保证数据在不同子 widget 中进行共享。这对于一些需要使用共享数据的场景非常有效,例如,在 Flutter SDK 中就是通过 InheritedWidget 来共享应用的主题与语言信息。
可能你还有点模糊,别急,下面我们通过一个简单的示例来了解 InheritedWidget。
示例
相信开始学 Flutter 时都看过官方的计数器示例。我们将官方提供的计数器示例使用 InheritedWidget 进行改造。
首先我们需要一个 CountInheritedWidget,它继承于 InheritedWidget。
class CountInheritedWidget extends InheritedWidget {CountInheritedWidget({@required this.count, Widget child})
: super(child: child);
// 共享数据,计数的数量
final int count;
// 统一的获取 CountInheritedWidget 实例, 方便树中子 widget 的获取共享数据
// 必须在 State 中调用才会有效
static CountInheritedWidget of(BuildContext context) {
// 调用共享数据的子 widget 将不会回调 didChangeDependencies 方法,即子 widget 将不会更新
// return context.getElementForInheritedWidgetOfExactType<CountInheritedWidget>().widget;
return context.dependOnInheritedWidgetOfExactType<CountInheritedWidget>();}
// true -> 通知树中依赖改共享数据的子 widget
@override
bool updateShouldNotify(CountInheritedWidget oldWidget) {return oldWidget.count != count;}
}
- 在 CountInheritedWidget 中提供共享计数的数量 count
- 同时为外部提供统一的获取 CountInheritedWidget 实例的 of 方法
- 最后再重写 updateShouldNotify 方法,来通知依赖该共享 count 的子 widget 进行更新
现在已经有了共享数据 count 的提供,接下来是在具体的子 widget 中进行使用。
我们抽离出一个 CountText 子 widget
class CountText extends StatefulWidget {
@override
_CountTextState createState() {return _CountTextState();
}
}
class _CountTextState extends State<CountText> {
@override
Widget build(BuildContext context) {return Text("count: ${CountInheritedWidget.of(context).count.toString()}");
}
@override
void didChangeDependencies() {super.didChangeDependencies();
print("didChangeDependencies");
}
}
- 内部引用了 CountInheritedWidget 中的共享数据 count,通过 of 方法获取 CountInheritedWidget 实例
- didChangeDependencies 可以用来监听子 widget 依赖是否反生改变
最后,我们再将 CountInheritedWidget 与 CountText 结合起来,通过简单的点击自增事件来看下效果
class CountWidget extends StatefulWidget {
@override
_CountState createState() {return _CountState();
}
}
class _CountState extends State<CountWidget> {
int count = 0;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Count App',
theme: new ThemeData(primarySwatch: Colors.blue),
home: Scaffold(
appBar: AppBar(title: Text("Count"),
),
body: Center(
child: CountInheritedWidget(
count: count,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[CountText(),
RaisedButton(onPressed: () => setState(() => count++),
child: Text("Increment"),
)
],
),
),
),
),
);
}
}
上面的层级关系是 CountText 刚好是 CountInheritedWidget 的子 widget。
现在我们通过点击事件直接改变外部的 count 值,如果 InheritedWidget 从上到下数据传到的效果能够生效,那么在 CountText 中引用的 count 将会与外部 count 同步,程序呈现的效果将会是自增的,同时由于依赖的 count 发生改变 CountText 中的 didChangeDependencies 也会回调。
我们直接运行一下
点击后的输出日志
I/flutter: didChangeDependencies
说明 InheritedWidget 的效果已经生效,通过 InheritedWidget 的使用,我们可以很方便的在嵌套下层的子 widget 中拿到上层的数据,或者说整个 widget 的共享数据。
分析
在依赖方式改变时子 widget 的 didChangeDependencies 会回调,但由于你可能会在该方法中做一些特殊的操作,例如网络请求。只是需要一次就可以。如果是套用我们上面的示例,将会在 count 子增时反复调用。
为了防止 didChangeDependencies 的调用,我们再来看 CountInheritedWidget 的 of 方法中注释的那部分
static CountInheritedWidget of(BuildContext context) {
// 调用共享数据的子 widget 将不会回调 didChangeDependencies 方法,即子 widget 将不会更新
// return context.getElementForInheritedWidgetOfExactType<CountInheritedWidget>().widget;
return context.dependOnInheritedWidgetOfExactType<CountInheritedWidget>();}
我们使用的是 dependOnInheritedWidgetOfExactType 方法,依赖的共享数据发生改变时会回调子 widget 中的 didChangeDependencies 方法,如果我们不想要子 widget 调用该方法,可以使用注释的代码,通过 getElementForInheritedWidgetOfExactType 方法来获取共享数据。
如果此时我们再运行一下项目,点击 count 自增,控制台将不会再输出日志。这样就可以解决 didChangeDependencies 的反复调用。
而这两个方法的主要区别是在 dependOnInheritedWidgetOfExactType 调用的过程中会进行注册依赖关系
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect}) {assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
所以 dependOnInheritedWidgetOfExactType 更新依赖的子 widget 中的 didChangeDependencies 方法。
思考下一个问题,虽然现在 didChangeDependencies 方法不会调用,但是 CountText 的 build 方法还是会执行。原因是在 CountWidget 中通过 setState 来改变 count 值,会重新 build 所用的子 widget。但我们真正想要的只是更新子 widget 中依赖的 CountInheritedWidget 的组件值。
那么如何解决呢?这里提供一个解决方案是为子 widget 提供缓存。可以通过封装一个简单的 StatefulWidget,将子 widget 缓存起来。如果对这块感兴趣的,可以期待我的后续文章。
推荐项目
下面介绍一个完整的 Flutter 项目,对于新手来说是个不错的入门。
flutter_github,这是一个基于 Flutter 的 Github 客户端同时支持 Android 与 IOS,支持账户密码与认证登陆。使用 dart 语言进行开发,项目架构是基于 Model/State/ViewModel 的 MSVM;使用 Navigator 进行页面的跳转;网络框架使用了 dio。项目正在持续更新中,感兴趣的可以关注一下。
当然如果你想了解 Android 原生,相信 flutter_github 的纯 Android 版本 AwesomeGithub 是一个不错的选择。
如果你喜欢我的文章模式,或者对我接下来的文章感兴趣,建议您关注我的微信公众号:【Android 补给站】
或者扫描下方二维码,与我建立有效的沟通,同时更快更准的收到我的更新推送。