Flutter笔记-深入分析Widget 2

作者: 叶落清秋 | 来源:发表于2019-02-20 16:15 被阅读27次

    ps: 文中flutter源码版本 1.0.0


    ProxyWidget

    代理控件,内部对原本的控件进行了包装处理

    abstract class ProxyWidget extends Widget {
      const ProxyWidget({ Key key, @required this.child }) : super(key: key);
    
      final Widget child;
    }
    

    这类控件的ProxyElement在build的时候直接将child返回了
    其有2个比较重要的子类, ParentDataWidget和InheritedWidget

    ParentDataWidget

    用在多孩子控件中,主要是获取父控件的ParentData

    这个控件用途比较广,如Positioned,可能你没用过,但布局控件Stack总有用过,他们是配套使用的
    Positioned之于Stack,就好比Colum/Row之于Expanded(Flexible),就好比Table之于TableCell,就好比CustomMultiChildLayout之于LayoutId


    我们就拿中文网上那个例子来分析:

    Stack(
        alignment:Alignment.center , //指定未定位或部分定位widget的对齐方式
        children: <Widget>[
          Container(child: Text("Hello world",style: TextStyle(color: Colors.white)),
            color: Colors.red,
          ),
          Positioned(
            left: 18.0,
            child: Text("I am Jack"),
          ),
          Positioned(
            top: 18.0,
            child: Text("Your friend"),
          )        
        ])
    

    为什么Positioned的位置不受Alignment.center的影响?
    我们先看Positioned:

    //光看泛型就知道与Stack渊源不浅
    class Positioned extends ParentDataWidget<Stack> {
      const Positioned({
        Key key,
        this.left,
        this.top,
        this.right,
        this.bottom,
        this.width,
        this.height,
        @required Widget child,
      //断言,意思是6个属性必须传至少一个
      }) : assert(left == null || right == null || width == null),
           assert(top == null || bottom == null || height == null),
           super(key: key, child: child);
      //省略了多个命名构造函数与工厂构造函数
      ...
      final double left;
      final double top;
      final double right;
      final double bottom;
      final double width;
      final double height;
    
      //继承方法,这个肯定就是影响的关键所在了
      @override
      void applyParentData(RenderObject renderObject) {
        assert(renderObject.parentData is StackParentData);
        final StackParentData parentData = renderObject.parentData;
        bool needsLayout = false;
        //判断父类传递的与自身属性的值是否一样,不一样则给父类的赋值,并且父控件重新摆放
        //注:默认值是为null的
        if (parentData.left != left) {
          parentData.left = left;
          needsLayout = true;
        }
    
        if (parentData.top != top) {
          parentData.top = top;
          needsLayout = true;
        }
    
        if (parentData.right != right) {
          parentData.right = right;
          needsLayout = true;
        }
    
        if (parentData.bottom != bottom) {
          parentData.bottom = bottom;
          needsLayout = true;
        }
    
        if (parentData.width != width) {
          parentData.width = width;
          needsLayout = true;
        }
    
        if (parentData.height != height) {
          parentData.height = height;
          needsLayout = true;
        }
    
        if (needsLayout) {
          final AbstractNode targetParent = renderObject.parent;
          if (targetParent is RenderObject)
            targetParent.markNeedsLayout();
        }
      }
    
      //我们只需弄懂原理,这个肯定与debug有关,不考虑的
      @override
      void debugFillProperties(DiagnosticPropertiesBuilder properties) {
        ...
      }
    }
    

    StackParentData是什么?这里只是存储值,与Positioned的属性对应

    class StackParentData extends ContainerBoxParentData<RenderBox> {
      double top;
      double right;
      double bottom;
      double left;
      double width;
    
      RelativeRect get rect => RelativeRect.fromLTRB(left, top, right, bottom);
    
      set rect(RelativeRect value) {
        top = value.top;
        right = value.right;
        bottom = value.bottom;
        left = value.left;
      }
      //使用Positioned的话,因为断言,6个属性必须传至少一个
      bool get isPositioned => top != null || right != null || bottom != null || left != null || width != null || height != null;
    
      @override
      String toString() {
        ...
      }
    }
    

    很好,大致思路已经清楚了,那么applyParentData(RenderObject renderObject)方法中,这个renderObject是谁?什么时候传递的?renderObject.parentData又是什么时候赋值的?
    我们现在从头开始分析:

    //Stack是一个MultiChildRenderObjectWidget
    class Stack extends MultiChildRenderObjectWidget {
      ...
      @override
      RenderStack createRenderObject(BuildContext context) {
        return RenderStack(
          alignment: alignment,
          textDirection: textDirection ?? Directionality.of(context),
          fit: fit,
          overflow: overflow,
        );
      }
      ...
    }
    

    既然是MultiChildRenderObjectWidget,根据之前的分析,我们从其的mount方法开始:

     @override
      void mount(Element parent, dynamic newSlot) {
        super.mount(parent, newSlot);
        //length个数是3,建立空列表
        _children = List<Element>(widget.children.length);
        Element previousChild;
        //循环开始查找
        for (int i = 0; i < _children.length; i += 1) {
          //widget.children[i],这里我们只找Positioned
          //这里传递前一个为了建立单链表
          final Element newChild = inflateWidget(widget.children[i], previousChild);
          //列表保存
          _children[i] = newChild;
          previousChild = newChild;
        }
      }
    

    进入inflateWidget方法

    @protected
      Element inflateWidget(Widget newWidget, dynamic newSlot) {
        ...
        final Element newChild = newWidget.createElement();
        assert(() { _debugCheckForCycles(newChild); return true; }());
        newChild.mount(this, newSlot);
        assert(newChild._debugLifecycleState == _ElementLifecycle.active);
        return newChild;
      }
    

    Positioned的createElement()是ParentDataElement<Stack>(this),因此找其的mount方法

      @override
      void mount(Element parent, dynamic newSlot) {
        //都是断言,省略
        ...
        super.mount(parent, newSlot);
      }  
    

    找到ParentDataElement的父类的父类ComponentElement(抽象类ProxyElement未实现此方法)

    @override
      void mount(Element parent, dynamic newSlot) {
        super.mount(parent, newSlot);
        assert(_child == null);
        assert(_active);
        _firstBuild();
        assert(_child != null);
      }
    
      void _firstBuild() {
        rebuild();
      }
    

    这一系列和前面分析RenderObject(事件分发)是一样的,最后会传递到Text里RichText的LeafRenderObjectElement中,所以从它的mount方法

     //父类RenderObjectElement中的方法
    @override
      void mount(Element parent, dynamic newSlot) {
        super.mount(parent, newSlot);
        _renderObject = widget.createRenderObject(this);
        assert(() { _debugUpdateRenderObjectOwner(); return true; }());
        assert(_slot == newSlot);
        //绑定RenderObject
        attachRenderObject(newSlot);
        _dirty = false;
      }
    

    跟进attachRenderObject(newSlot)方法

      @override
      void attachRenderObject(dynamic newSlot) {
        assert(_ancestorRenderObjectElement == null);
        _slot = newSlot;
        _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
        _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
        final ParentDataElement<RenderObjectWidget> parentDataElement = _findAncestorParentDataElement();
        if (parentDataElement != null)
          _updateParentData(parentDataElement.widget);
      }
    

    _findAncestorRenderObjectElement() 是为了找到前一个RenderObjectElement,即Stack中的MultiChildRenderObjectElement

    RenderObjectElement _findAncestorRenderObjectElement() {
        Element ancestor = _parent;
        //循环找到一个为RenderObjectElement的父Element
        while (ancestor != null && ancestor is! RenderObjectElement)
          ancestor = ancestor._parent;
        return ancestor;
      }
    

    至于_parent是哪赋值的呢,之前的inflateWidget方法里有一个newChild._activateWithParent(this, newSlot);

    void _activateWithParent(Element parent, dynamic newSlot) {
        assert(_debugLifecycleState == _ElementLifecycle.inactive);
        _parent = parent;
        ...
      }
    

    a. parentData赋值

    _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot)即调用MultiChildRenderObjectElement中的方法:

    @override
      void insertChildRenderObject(RenderObject child, Element slot) {
        final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject = this.renderObject;
        assert(renderObject.debugValidateChild(child));
        //这个renderObject是Stack的RenderStack
        renderObject.insert(child, after: slot?.renderObject);
        assert(renderObject == this.renderObject);
      }
    

    insert不仅加入了列表,还执行了重新摆放请求

      void insert(ChildType child, { ChildType after }) {
        ...
        adoptChild(child);
        _insertIntoChildList(child, after: after);
      }
    

    跟进adoptChild方法:

    @override
      void adoptChild(RenderObject child) {
        assert(_debugCanPerformMutations);
        assert(child != null);
        //设置ParentData
        setupParentData(child);
        super.adoptChild(child);
        markNeedsLayout();
        markNeedsCompositingBitsUpdate();
        markNeedsSemanticsUpdate();
      }
    

    再跟进setupParentData方法

    void setupParentData(covariant RenderObject child) {
        assert(_debugCanPerformMutations);
        if (child.parentData is! ParentData)
          child.parentData = ParentData();
      }
    

    很明显不对,我们要的不是ParentData()对象,而是StackParentData(),会不会是RenderStack重写了该方法

    @override
      void setupParentData(RenderBox child) {
        if (child.parentData is! StackParentData)
          child.parentData = StackParentData();
      }
    

    NICE,已经解决一个问题了!

    b. applyParentData调用流程

    ParentDataElement<RenderObjectWidget> _findAncestorParentDataElement() {
        Element ancestor = _parent;
        while (ancestor != null && ancestor is! RenderObjectElement) {
          //寻找属于ParentDataElement的父Element
          if (ancestor is ParentDataElement<RenderObjectWidget>)
            return ancestor;
          ancestor = ancestor._parent;
        }
        return null;
      }
    

    基本和前面那个一样,就是找到对象不一样,找到了Positioned的ParentDataElement<Stack>,然后调用_updateParentData(parentDataElement.widget)方法

    void _updateParentData(ParentDataWidget<RenderObjectWidget> parentData) {
        //parentData就是Positioned,renderObject则是Text的RenderParagraph
        parentData.applyParentData(renderObject);
      }
    

    OK,流程就走完了

    InheritedWidget(填坑)

    数据共享的控件

    一直不明白这控件有什么优势,要实现其类似的效果,方式其实有很多
    可能最大的优点就是使用起来比较简单吧,而且源码结构上都用上了它,既然官方都提供了,那还不乖乖的使用


    还是以中文网上的简单计数器例子为例,进行源码分析:

    class ShareDataWidget extends InheritedWidget {
      ShareDataWidget({
        @required this.data,
        Widget child
      }) :super(child: child);
    
      int data; //需要在子树中共享的数据,保存点击次数
    
      //定义一个便捷方法,方便子树中的widget获取共享数据  
      static ShareDataWidget of(BuildContext context) {
        return context.inheritFromWidgetOfExactType(ShareDataWidget);
      }
    
      //该回调决定当data发生变化时,是否通知子树中依赖data的Widget  
      @override
      bool updateShouldNotify(ShareDataWidget old) {
        //如果返回false,则子树中依赖(build函数中有调用)本widget
        //的子widget的`state.didChangeDependencies`会被调用
        return old.data != data;
      }
    }
    

    可以注意到有2个重要的方法:inheritFromWidgetOfExactType和updateShouldNotify

    class InheritedWidgetTestRoute extends StatefulWidget {
      @override
      _InheritedWidgetTestRouteState createState() => new _InheritedWidgetTestRouteState();
    }
    
    class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
      int count = 0;
    
      @override
      Widget build(BuildContext context) {
        return  Center(
          child: ShareDataWidget( //使用ShareDataWidget
            data: count,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.only(bottom: 20.0),
                  child: _TestWidget(),//子widget中依赖ShareDataWidget
                ),
                RaisedButton(
                  child: Text("Increment"),
                  //每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新  
                  onPressed: () => setState(() => ++count),
                )
              ],
            ),
          ),
        );
      }
    }
    

    每点击一次按钮,就会调用一次setState,InheritedWidgetTestRoute就会rebuild一次,再次调用build方法,此时ShareDataWidget的data将赋予新的count值,根据前面的分析,可以知道会调用update方法

    abstract class InheritedWidget extends ProxyWidget {
      const InheritedWidget({ Key key, Widget child })
        : super(key: key, child: child);
    
      @override
      InheritedElement createElement() => InheritedElement(this);
    
      @protected
      bool updateShouldNotify(covariant InheritedWidget oldWidget);
    }
    

    寻找InheritedElement中的update方法,在其父类ProxyElement中找到

    @override
      void update(ProxyWidget newWidget) {
        final ProxyWidget oldWidget = widget;
        assert(widget != null);
        assert(widget != newWidget);
        super.update(newWidget);
        assert(widget == newWidget);
        updated(oldWidget);
        _dirty = true;
        rebuild();
      }
    

    调用InheritedElement的updated方法

    @override
      void updated(InheritedWidget oldWidget) {
        //这里调用updateShouldNotify,如果数据不同,则进行更新
        if (widget.updateShouldNotify(oldWidget))
          super.updated(oldWidget);
      }
    

    继续根据super.updated(oldWidget)

    @protected
      void updated(covariant ProxyWidget oldWidget) {
        notifyClients(oldWidget);
      }
    

    继续调用InheritedElement的notifyClients方法

    @override
      void notifyClients(InheritedWidget oldWidget) {
        //省去断言
        ...
        //执行列表遍历
        for (Element dependent in _dependents.keys) {
          //省去断言
          ...
          notifyDependent(oldWidget, dependent);
        }
    
     //调用元素的didChangeDependencies方法
    @protected
      void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
        dependent.didChangeDependencies();
      }
    

    这里会有疑问:_dependents是什么?数据是什么时候添加的?

    final Map<Element, Object> _dependents = HashMap<Element, Object>();
    

    _dependents是一个HashMap键值表,至于怎么赋值的我们要看另一个方法,BuildContext是一个接口,它的子类只有Element,所以查找其的方法:

    @override
      InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
        assert(_debugCheckStateIsActiveForAncestorLookup());
        //查找表中targetType类型对应的InheritedElement
        final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
        if (ancestor != null) {
          assert(ancestor is InheritedElement);
          return inheritFromElement(ancestor, aspect: aspect);
        }
        _hadUnsatisfiedDependencies = true;
        return null;
      }
    

    _inheritedWidgets的值是什么时候赋值的稍后分析,先分析完当前这个:

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

    跟进updateDependencies方法:

    @protected
      void updateDependencies(Element dependent, Object aspect) {
        setDependencies(dependent, null);
      }
    
    @protected
      void setDependencies(Element dependent, Object value) {
        _dependents[dependent] = value;
      }
    

    已经找到了_dependents的赋值,只有key,value都是null。再回到前面,ancestor值找的是谁?_inheritedWidgets值是什么时候赋的?
    InheritedElement创建时会调用mount方法:

    //Element中方法
    void mount(Element parent, dynamic newSlot) {
        ...
        _updateInheritance();
        assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; }());
      }
    

    InheritedElement的_updateInheritance()中进行赋值

    @override
      void _updateInheritance() {
        assert(_active);
        //父类向下传递map
        final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
        //有值则拷贝,无值则初始化
        if (incomingWidgets != null)
          _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
        else
          _inheritedWidgets = HashMap<Type, InheritedElement>();
       //将当前InheritedElement加入到map中
        _inheritedWidgets[widget.runtimeType] = this;
      }
    

    HashMap的from拷贝方法:

    factory HashMap.from(Map other) {
        Map<K, V> result = new HashMap<K, V>();
        //循环逐值拷贝
        other.forEach((k, v) {
          result[k] = v;
        });
        return result;
      }
    

    至此就分析完了,其实也就是内部有一个HashMap保存着当前的对象,然后通过该对象得到它要保存的数据
    因为key是不能重复的,所以只能保存一个值,也就是说当有多个相同的InheritedWidget,map中的value进行覆盖,只保留最后一个。对于实际的InheritedWidget来说只取最近的
    InheritedWidget的作用域只能包括自己及自己的子节点,所以InheritedWidget在树中只能向下传递


    上一篇:深入分析Widget 1
    这里有个使用的实例:天气查询APP

    相关文章

      网友评论

        本文标题:Flutter笔记-深入分析Widget 2

        本文链接:https://www.haomeiwen.com/subject/mnyaeqtx.html