InheritedWidgetFlutter中非常重要的一个功能型组件,它提供了一种在Widget树中从上到下共享数据的方式,比如我们在一个应用的根Widget中通过InheritedWidget共享了一个数据,那么在这个应用的任意一个子Widget中都可以获取到该数据!在Flutter中的应用主题Theme和当前语言Locale正是通过InheritedWidget来共享信息的;

InheritedWidget

didChangeDependencies

我们在之前介绍StatefulWidget的生命周期时,曾经提到过State对象的didChangeDependencies回调方法,它会在依赖发生改变是被Flutter框架调用。这个”依赖”指的是子Widget是否使用了父WidgetInheritedWidget的数据!如果使用了,则代表有依赖关系,否则没有依赖关系;这种机制可以使子组件在所依赖的InheritedWidget发生变化是来更新自身!

没有依赖关系

  1. class InheritedDemo extends StatefulWidget {
  2. @override
  3. _InheritedDemoState createState() => _InheritedDemoState();
  4. }
  5. class _InheritedDemoState extends State<InheritedDemo> {
  6. int _count = 0;
  7. @override
  8. Widget build(BuildContext context) {
  9. return Row(
  10. children: [
  11. Container(child: TextA(count: _count), width: 60, height: 30, alignment: Alignment.center,),
  12. ElevatedButton(onPressed: () {
  13. setState(() {
  14. _count++;
  15. });
  16. }, child: const Icon(Icons.add))
  17. ],
  18. );
  19. }
  20. }
  21. class TextA extends StatelessWidget {
  22. final int? count;
  23. TextA({this.count});
  24. @override
  25. Widget build(BuildContext context) {
  26. return TextB(count: count);
  27. }
  28. }
  29. class TextB extends StatefulWidget {
  30. final int? count;
  31. TextB({this.count});
  32. @override
  33. State<StatefulWidget> createState() {
  34. // TODO: implement createState
  35. return TextBState();
  36. }
  37. }
  38. class TextBState extends State<TextB> {
  39. @override
  40. void didChangeDependencies() {
  41. print('didChangeDependencies 执行了');
  42. super.didChangeDependencies();
  43. }
  44. @override
  45. Widget build(BuildContext context) {
  46. print('build 执行了');
  47. return Text('${widget.count}');
  48. }
  49. }

我们看如上这段代码,在InheritedDemo中添加了一个TextAElevatedButton,点击按钮时改变界面上Text的值,但是TextA并不显示Text,而是将count传给了TextB,然后由TextB来显示Text,此时我们点击按钮,查看效果:
iShot2021-11-30 11.28.29.gif
我们发现didChangeDependencies方法在项目运行之后,在build执行之前执行了一次,之后我们点击按钮,此方法没有再执行;也就是当前TextBInheritedDemo之间并没有依赖关系;

有依赖关系

我们来创建一个InheritedData继承InheritedWidget,其实现如下:
image.png

  • InheritedData需要集成自InheritedWidget
  • 构造方法中必须添加required Widget child,后边必须跟上super(child: child)
  • 子Widget通过of方法来获取共享数据;
  • 必须重写updateShouldNotify方法,方法返回true时,当前共享数据的clickCount发生改变时,将会通知子Widget中依赖clickCountWidget(此时didChangeDependenciesbuild方法都会调用);否则不会通知(此时只会调用build方法);

我们将原来的代码中InheritedDemo代码修改如下:
image.png
build方法中使用InheritedData,将原来的Row放进InheritedDatachild属性中,将_count赋值给InheritedDataclickCount属性;

TextB的代码修改如下:
image.png
最终在TextB中的build方法中,引用InheritedData的数据给Text赋值;

最终运行及打印结果如下:
iShot2021-11-30 13.48.03.gif
didChangeDependencies方法执行了,说明存在依赖关系;

didChangeDependencies中可以做什么

一般来说,很少会在子Widget中重写此方法,因为在依赖改变后,Flutter框架也会调用build()方法重新构建树。但是如果需要在依赖发生改变后执行一些昂贵或者耗时的操作,比如网络请求,这时候最好的方法就是在didChangeDependencies方法中执行操作,这样可以避免每次build()都执行这些耗时操作;

深入了解InheritedWidget

如果我们指向要在TextB中引用InheritedData的数据,但是不希望在InheritedData发生改变时调用TextBStatedidChangeDependencies方法,那么我们应该如何做呢?

我们可以将InheritedDataof方法修改如下:

  1. static InheritedData of(BuildContext context) {
  2. final InheritedData? result = context.getElementForInheritedWidgetOfExactType<InheritedData>()!.widget as InheritedData?;
  3. assert(result != null, 'No InheritedData found in context');
  4. return result!;
  5. }

of方法中,将获取InheritedData对象的方式从原来的:

  1. final InheritedData? result = context.dependOnInheritedWidgetOfExactType<InheritedData>();

修改为:

  1. final InheritedData? result = context.getElementForInheritedWidgetOfExactType<InheritedData>()!.widget as InheritedData?;

其他调用不做修改,我们看一下结果:
iShot2021-11-30 14.25.33.gif
那么这两个方法有什么区别呢?我们对比一下这两个方法的源码:

  1. @override
  2. InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
  3. final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
  4. return ancestor;
  5. }
  1. @override
  2. InheritedWidget dependOnInheritedWidgetOfExactType({ Object aspect }) {
  3. assert(_debugCheckStateIsActiveForAncestorLookup());
  4. final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
  5. //多出的部分
  6. if (ancestor != null) {
  7. return dependOnInheritedElement(ancestor, aspect: aspect) as T;
  8. }
  9. _hadUnsatisfiedDependencies = true;
  10. return null;
  11. }

我们发现dependOnInheritedWidgetOfExactType方法多调用了dependOnInheritedElement方法,此方法源码如下:

  1. @override
  2. InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
  3. assert(ancestor != null);
  4. _dependencies ??= HashSet<InheritedElement>();
  5. _dependencies.add(ancestor);
  6. ancestor.updateDependencies(this, aspect);
  7. return ancestor.widget;
  8. }

dependOnInheritedElement方法中主要是注册了依赖关系!所以在调用dependOnInheritedWidgetOfExactType方法时,InheritedWidget和依赖它的子部件关系将会注册完成,之后当InheritedWidget发生变化时,就会更新组件的依赖关系,也就是会调用子部件的didChangeDependencies方法和build方法。

需要注意的是,虽然将调用方式改为getElementForInheritedWidgetOfExactType方法之后,didChangeDependencies方法虽然不会执行,但是build方法依然会执行!这是因为当我们点击按钮时,调用了_InheritedDemoStatesetState方法,此时将会重新构建整个页面,由于TextATextB并没有任何缓存,所以他们都会被重新构建,因此TextATextBbuild方法都会执行;