最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • InheritedWidget的使用和源码分析

    正文概述 掘金(chonglingliu)   2021-03-26   666

    在用Flutter进行界面开发时,我们经常会遇到数据传递的问题。但是由于Flutter采用树形结构,造成数据传递的链条有时候会很长,代码写起来也很不方便。

    InheritedWidget可以让它的子节点能访问到它的公开属性,从而实现数据的跨Widget的传递。

    InheritedWidget使用

    我们先用一个Demo来看看InheritedWidget的使用方法。Demo如下,InheritedWidget子类InfoWidgetnumber数值变化后,底下的三个InfoChildWidget显示的number也会变化。

    InheritedWidget的使用和源码分析

    接下来我们来写代码。

    • 由于InheritedWidget是抽象类,我们创建一个继承 自InheritedWidgetInfoWidget
    class InfoWidget extends InheritedWidget {
      // 1
      final int number;
        
      // 2    
      InfoWidget({Key key, @required this.number, @required child})
          : super(key: key, child: child);
        
      //3    
      @override
      bool updateShouldNotify(InfoWidget oldWidget) {
        return number != oldWidget.number;
      }
      
      // 4
      static InfoWidget of(BuildContext context) {
        return context.dependOnInheritedWidgetOfExactType(); 
      }
      
    }
    

    代码说明:

    1. number就是定义的共享的数据;
    2. InfoWidget的构造函数中,有三个参数除了key外,都是必传参数,number是外部传入的给InfoWidget共享的数据,child子Widget
    1. InheritedWidget的子类需要重写updateShouldNotify方法,这个方法如果返回true,则会回调StatefulElementstatedidChangeDependencies方法;
    2. of这个静态方法是留给子Widget使用的,子Widget可以通过它获取到InheritedWidget的共享数据。
    • 建一个Widget,它可以显示InfoWidget共享的数据
    class InfoChildWidget extends StatelessWidget {
      
      // 1
      const InfoChildWidget();
    
      @override
      Widget build(BuildContext context) {
        // 2
        final int number = InfoWidget.of(context).number;
        return Text("$number", style: TextStyle(color: Colors.amber, fontSize: 40));
      }
    }
    
    1. 使用InfoChildWidget常量构造函数是为了解决不必要的重建和销毁。
    2. InfoWidget.of(context)就是上面提到的给子Widget使用的of静态方法,然后取到number就可以直接显示了。
    • 使用
    InfoWidget(
        number: _number,
            child: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    InfoChildWidget(),
                    InfoChildWidget(),
                    InfoChildWidget(),
                  ],
                ),
            ),
        )
    

    使用的时候是将InfoChildWidget做为InfoWidget子Widget,我这里特意中间加了CenterColumn,就是为了指出InfoChildWidget不一定需要是直接子Widget

    • 所有代码如下:
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      int _number = 0;
      void _incrementCounter() {
        _number = Random().nextInt(100);
        setState(() {});
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: InfoWidget(
              number: _number,
              child: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    InfoChildWidget(),
                    InfoChildWidget(),
                    InfoChildWidget(),
                  ],
                ),
              ),
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      }
    }
    
    <!-- InfoWidget -->
    class InfoWidget extends InheritedWidget {
      final int number;
    
      InfoWidget({Key key, @required this.number, @required child})
          : super(key: key, child: child);
    
      @override
      bool updateShouldNotify(InfoWidget oldWidget) {
        return number != oldWidget.number;
      }
    
      static InfoWidget of(BuildContext context) {
        return context.dependOnInheritedWidgetOfExactType();
      }
    }
    
    <!-- InfoChildWidget -->
    class InfoChildWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final int number = InfoWidget.of(context).number;
        return Text("$number", style: TextStyle(color: Colors.amber, fontSize: 40));
      }
    }
    

    效果如下:

    InheritedWidget的使用和源码分析

    InheritedWidget源码分析

    设置inheritedWidgets

    每个Element都有一个keyInheritedWidget类型,值为InheritedElementMap属性_inheritedWidgets

    Map<Type, InheritedElement>? _inheritedWidgets;
    

    每个Widget生成的Element挂载到Element Tree上的时候都会调用mount方法:

    <!-- Element -->
    void mount(Element? parent, dynamic newSlot) {
        _updateInheritance();
    }
    

    mount方法会调用_updateInheritance方法:

    <!-- Element -->
    void _updateInheritance() {
        _inheritedWidgets = _parent?._inheritedWidgets;
    }
    

    如果不是InheritedElement,则_inheritedWidgets都指向父Element_inheritedWidgets

    <!-- InheritedElement -->
    void _updateInheritance() {
        final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
        if (incomingWidgets != null)
          _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
        else
          _inheritedWidgets = HashMap<Type, InheritedElement>();
        _inheritedWidgets![widget.runtimeType] = this;
    }
    

    如果是InheritedElement,先拷贝一份父节点的_inheritedWidgets, 然后添加或者替换key为widget.runtimeType,值为InheritedElement的键值对。

    InheritedWidget的使用和源码分析

    子ElementInheritedElement并添加依赖

    我们来看看of类方法调用的dependOnInheritedWidgetOfExactType方法:

    <!-- Element -->
    Set<InheritedElement>? _dependencies;
    
    T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
        // 1
        final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
        if (ancestor != null) {
          // 2
          return dependOnInheritedElement(ancestor, aspect: aspect) as T;
        }
        _hadUnsatisfiedDependencies = true;
        return null;
    }
    
    InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
        _dependencies ??= HashSet<InheritedElement>();
        // 3
        _dependencies!.add(ancestor);
        // 3
        ancestor.updateDependencies(this, aspect);
        return ancestor.widget;
    }
    
    1. _inheritedWidgets这个Map中找到类型对应的InheritedElement
    2. 如果找到则调用dependOnInheritedElement方法;dependOnInheritedElement方法主要是将InheritedElement加入到_dependencies这个Set中,然后InheritedElement调用updateDependencies方法把子Element加入到_dependents中。
    <!-- InheritedElement -->
    void updateDependencies(Element dependent, Object? aspect) {
        setDependencies(dependent, null);
    }
    
    void setDependencies(Element dependent, Object? value) {
        _dependents[dependent] = value;
    }
    
    final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
    

    InheritedWidget的使用和源码分析

    InheritedElement数据变化调用StatefulElementdidChangeDependencies方法:

    InheritedWidget调用build方法的时候,会调用notifyClients方法:

    void updated(InheritedWidget oldWidget) {
        if (widget.updateShouldNotify(oldWidget))
          super.updated(oldWidget);
    }
      
    void updated(covariant ProxyWidget oldWidget) {
        notifyClients(oldWidget);
    }
    

    notifyClients方法会对_dependents中的每个子Element调用notifyDependent方法,子Element会调用didChangeDependencies方法:

    void notifyClients(InheritedWidget oldWidget) {
        for (final Element dependent in _dependents.keys) {
          notifyDependent(oldWidget, dependent);
        }
    }
    
    void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
        dependent.didChangeDependencies();
    }
    

    子Element调用didChangeDependencies最后会重新构建:

    void didChangeDependencies() {
        markNeedsBuild();
    }
    

    子ElementStateFulElement时,会将_didChangeDependencies置为true;

    void didChangeDependencies() {
        super.didChangeDependencies();
        _didChangeDependencies = true;
    }
    

    当重新构建时,StateFulElement会调用statedidChangeDependencies方法。

    void performRebuild() {
        if (_didChangeDependencies) {
          state.didChangeDependencies();
          _didChangeDependencies = false;
        }
        super.performRebuild();
    }
    

    InheritedWidget的使用和源码分析

    总结

    InheritedWidget传递参数的方案只是把传参从Constructor变成了BuildContext。但是它还是有些的不完善的地方:

    1. 某个类型的InheritedWidget只能获取到最近的那一个;
    2. 重新构建没法只重构依赖InheritedWidget子Widget,性能上不是太好。

    起源地下载网 » InheritedWidget的使用和源码分析

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元