Flutter 之 InheritWidget 源码浅析

简介: Flutter 之 InheritWidget 源码浅析

InheritWidget 实现局部刷新必须注意到两个点:

  • InheritWidget 下的子 widget 必须是缓存过的
  • InheritWidget 下的子 widget 需添加进 _dependencies 集合中
  • InheritWidget 需要 rebuild


1、为什么 InheritWidget 下的子 widget 必须是缓存过的?


我们看下未做 widget 缓存的例子:


class InheritWidgetState extends State<InheritWidgetDemo> {
  int count = 0;
  @override
  Widget build(BuildContext context) {
    return Column(
        children: [
          ShareDataWidget(data: count, child: _TestWidget()),
          RaisedButton( child: Text("Increment"),onPressed: () => setState(() => ++count), // 触发 rebuild
          )
        ]);}}
class _TestWidget extends StatefulWidget {
    @override
    _TestWidgetState createState() => new _TestWidgetState();}
class  _TestWidgetState extends State<_TestWidget> {
    @override
    Widget build(BuildContext context) {  
      print("我被 build 了");
      return Text(ShareDataWidget.of(context,rebuild:true).data.toString());}}
复制代码
  • 如果通过日志去查看的话,你会发现没有任何问题,单击 Increment , print 确实打出了被 build 的日志,你可能会觉得 _TestWidgetStaterebuild 设置为了true ,他就应该被 rebuild,没有任何问题
  • 但当把 rebuild 设置为 false 呢?按照我们对 InheritWidget 的理解来看,_TestWidgetState不应该被触发 build 操作,然而事实是,_TestWidgetState 仍然被触发,主要原因是 widget 未缓存,setState 会触发 widget 以及子 widget 的 build 操作,然后每次都会重新构建 _TestWidget

2、 如何缓存 widget


class InheritWidgetDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return HomePage(
        child:  WidgetA()
    );
  }
}
class HomePage extends StatefulWidget {
      HomePage({Key key, this.child, }) : super(key: key);
      // 缓存的 widget
      final Widget child;
      @override
      HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
   int counter = 0;
    void _incrementCounter() {
          setState(() {   ++counter;  });
     }
   @override 
   Widget build(BuildContext context) {
          print("home  ${widget.child.hashCode}"); // hashcode 一致
          return _MyInheritedWidget(
            data: this, child: widget.child,
   );}}
复制代码
  • 为什么这么写可以?主要是 setState 触发的是  HomePage 的 build,并不会影响到 InheritWidgetDemo ,又因为 widget 是在 InheritWidgetDemo 创建的,所以 HomePage 拿到的一直是没有变化过的 widget,也可以通过打印 widget.hashCode 来验证一下是否一致


3、InheritWidget 下的子 widget 需添加进 _dependencies 集合中


class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
           _MyInheritedWidget.of(context,rebuild: true).data.counter.toString(),
        ),
    );
  }
}
复制代码
  • 如上是通过 InheritWidget 来实现数据展示,rebuild 为 true 即为 InheritWidget 发生数据改变时需要刷新
static _MyInheritedWidget of(BuildContext context, {bool rebuild = true}) {
        if (rebuild) {
          return (context.dependOnInheritedWidgetOfExactType<_MyInheritedWidget>());
        }
        return (context.getElementForInheritedWidgetOfExactType<_MyInheritedWidget>()).widget;
  }
复制代码
  • rebuild 为 true 时,调用的是  dependOnInheritedWidgetOfExactType 方法
@override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
        assert(ancestor != null);
        _dependencies ??= HashSet<InheritedElement>();
       // 将当前 element 添加进 _dependencies 集合中
        _dependencies!.add(ancestor);
        ancestor.updateDependencies(this, aspect);
        return ancestor.widget;
  }
  @override
  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
        assert(_debugCheckStateIsActiveForAncestorLookup());
        final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
        if (ancestor != null) {
          assert(ancestor is InheritedElement);
          return dependOnInheritedElement(ancestor, aspect: aspect) as T;
        }
        _hadUnsatisfiedDependencies = true;
        return null;
  }
复制代码
  • context 的实现类是 element ,最终会将当前 widget 的 element 缓存到 _dependencies 集合中

4、如何触发局部刷新


class HomePageState extends State<HomePage> {
   int counter = 0;
    void _incrementCounter() {
          setState(() {   ++counter;  });
     }
   @override 
   Widget build(BuildContext context) {
          return _MyInheritedWidget(
            data: this, child: widget.child,
   );}}
复制代码
  • 在调用 _incrementCounter 方法触发  setState 时,会触发 HomePageState 的 rebuild 操作,从而影响到子 widget 的 _MyInheritedWidget rebuild,也就是 InheritWidget


abstract class ProxyElement extends ComponentElement {
   ....
     @override
  void update(ProxyWidget newWidget) {
    final ProxyWidget oldWidget = widget;
    ...
    updated(oldWidget);
    _dirty = true;
    rebuild();
  }
复制代码
  • InheritWidget 的 element 是 InheritElement ,继承的是 ProxyElement,最终会走到 update 方法中来更新当前的 widget,可以直接看 updated 方法


-> 父类 ProxyElement  
@protected
  void updated(covariant ProxyWidget oldWidget) {
     notifyClients(oldWidget);
  }
-> 子类 InheritElement
  @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
 }  
复制代码
  • 子类实现了父类的 updated 方法,子类会对 InheritWidget 的 updateShouldNotify 进行判断,如果返回 true ,则子 wdiget 会调用 didChangeDependencies 方法
  • 父类 updated 会触发 notifyClients 方法,notifyClients 是交由子类 InheritElement 来实现的
@override
void notifyClients(InheritedWidget oldWidget) {
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (final Element dependent in _dependents.keys) {
      ....
      // check that it really depends on us
      assert(dependent._dependencies!.contains(this));
      notifyDependent(oldWidget, dependent);
    }
  }
}
复制代码
  • notifyClients 会遍历 _dependencies 集合的 element 元素,并判断 element 是否包含当前 rebuild 的 InheritWidget , 然后调用  notifyDependent 方法
@protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }
复制代码
  • notifyDependent 会调用 element 的 didChangeDependencies 方法
@mustCallSuper
  void didChangeDependencies() {
    assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
    assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
    markNeedsBuild();// build
  }
复制代码
  • 进入到 didChangeDependencies 方法中查看发现,element 会触发 markNeedsBuild 进行重新构建,也就是局部 widget 刷新


5、如何使局部刷新失效


1、InheritWidget 的 updateShouldNotify 实现类返回 false

2、不将 widget 添加到 _dependencies 集合中,可以采用 getElementForInheritedWidgetOfExactType

目录
相关文章
|
移动开发 Dart 前端开发
从架构到源码:一文了解Flutter渲染机制
Flutter从本质上来讲还是一个UI框架,它解决的是一套代码在多端渲染的问题。在渲染管线的设计上更加精简,加上自建渲染引擎,相比ReactNative、Weex以及WebView等方案,具有更好的性能体验。本文将从架构和源码的角度详细分析Flutter渲染机制的设计与实现。较长,同学们可收藏后再看。
7525 1
从架构到源码:一文了解Flutter渲染机制
|
3月前
|
开发工具
Flutter-AnimatedWidget组件源码解析
Flutter-AnimatedWidget组件源码解析
179 60
|
4月前
|
安全 数据处理 开发者
Flutter相关痛点解决问题之iBox模块的源码结构的设计如何解决
Flutter相关痛点解决问题之iBox模块的源码结构的设计如何解决
|
JavaScript Java 关系型数据库
基于微服务、SpringBoot、Vue、Flutter、MySQL智慧工地系统源码
B/S架构,两个终端:PC后台管理端、APP移动端 基于微服务、Java、SpringBoot、Vue、Flutter、MySQL开发的智慧工地系统源码
|
Dart JavaScript Java
Flutter(二)——解析main.dart源码
Flutter(二)——解析main.dart源码
274 0
Flutter(二)——解析main.dart源码
|
Android开发 开发者 iOS开发
源码分享-一个帮助flutter开发者快速上手的app
源码分享-一个帮助flutter开发者快速上手的app
251 0
源码分享-一个帮助flutter开发者快速上手的app
Flutter Widget源码解析及实战(二)
Flutter Widget源码解析及实战(二)
196 0
Flutter Widget源码解析及实战(二)
|
缓存 Android开发
Flutter Widget源码解析及实战(一)
Flutter Widget源码解析及实战(一)
348 0
Flutter Widget源码解析及实战(一)
|
Dart 开发工具 开发者
【Flutter】Flutter 拍照示例 ( 拍照并获取照片源码示例 | image_picker: ^0.5.2 版本 )
【Flutter】Flutter 拍照示例 ( 拍照并获取照片源码示例 | image_picker: ^0.5.2 版本 )
227 0
【Flutter】Flutter 拍照示例 ( 拍照并获取照片源码示例 | image_picker: ^0.5.2 版本 )
|
Dart API Android开发
【Flutter】Flutter 拍照示例 ( Flutter 插件配置 | Flutter 插件源码示例 | iOS 应用配置 | Android 应用配置 )(二)
【Flutter】Flutter 拍照示例 ( Flutter 插件配置 | Flutter 插件源码示例 | iOS 应用配置 | Android 应用配置 )(二)
304 0
【Flutter】Flutter 拍照示例 ( Flutter 插件配置 | Flutter 插件源码示例 | iOS 应用配置 | Android 应用配置 )(二)