likes
comments
collection
share

Flutter(三十三)-InheritedWidget数据共享

作者站长头像
站长
· 阅读数 1

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

InheritedWidget

didChangeDependencies

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

没有依赖关系

class InheritedDemo extends StatefulWidget {
  @override
  _InheritedDemoState createState() => _InheritedDemoState();
}

class _InheritedDemoState extends State<InheritedDemo> {
  int _count = 0;
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Container(child: TextA(count: _count), width: 60, height: 30, alignment: Alignment.center,),
        ElevatedButton(onPressed: () {
          setState(() {
            _count++;
          });
        }, child: const Icon(Icons.add))
      ],
    );
  }
}

class TextA extends StatelessWidget {
  final int? count;
  TextA({this.count});

  @override
  Widget build(BuildContext context) {
    return TextB(count: count);
  }
}

class TextB extends StatefulWidget {

  final int? count;
  TextB({this.count});

  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return TextBState();
  }
}

class TextBState extends State<TextB> {

  @override
  void didChangeDependencies() {
    print('didChangeDependencies 执行了');
    super.didChangeDependencies();
  }

  @override
  Widget build(BuildContext context) {
    print('build 执行了');
    return Text('${widget.count}');
  }
}

我们看如上这段代码,在InheritedDemo中添加了一个TextAElevatedButton,点击按钮时改变界面上Text的值,但是TextA并不显示Text,而是将count传给了TextB,然后由TextB来显示Text,此时我们点击按钮,查看效果:

Flutter(三十三)-InheritedWidget数据共享

我们发现didChangeDependencies方法在项目运行之后,在build执行之前执行了一次,之后我们点击按钮,此方法没有再执行;也就是当前TextBInheritedDemo之间并没有依赖关系;

有依赖关系

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

Flutter(三十三)-InheritedWidget数据共享

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

我们将原来的代码中InheritedDemo代码修改如下:

Flutter(三十三)-InheritedWidget数据共享

build方法中使用InheritedData,将原来的Row放进InheritedDatachild属性中,将_count赋值给InheritedDataclickCount属性;

TextB的代码修改如下:

Flutter(三十三)-InheritedWidget数据共享

最终在TextB中的build方法中,引用InheritedData的数据给Text赋值;

最终运行及打印结果如下:

Flutter(三十三)-InheritedWidget数据共享

didChangeDependencies方法执行了,说明存在依赖关系;

didChangeDependencies中可以做什么

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

深入了解InheritedWidget

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

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

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

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

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

修改为:

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

其他调用不做修改,我们看一下结果:

Flutter(三十三)-InheritedWidget数据共享

那么这两个方法有什么区别呢?我们对比一下这两个方法的源码:

@override
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
  final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
  return ancestor;
}
@override
InheritedWidget dependOnInheritedWidgetOfExactType({ Object aspect }) {
  assert(_debugCheckStateIsActiveForAncestorLookup());
  final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
  //多出的部分
  if (ancestor != null) {
    return dependOnInheritedElement(ancestor, aspect: aspect) as T;
  }
  _hadUnsatisfiedDependencies = true;
  return null;
}

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

@override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }

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

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