Flutter笔记-深入分析Widget 2

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总有用过,他们是配套使用的
Stack之于Positioned,就好比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调用流程

之前分析到在attachRenderObject方法中会调用_findAncestorParentDataElement()方法

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

  • StatelessWidget
    无状态控件,内部控件
  • StatefulWidget
    状态控件,生命周期交给State
  • RenderObjectWidget
    渲染控件,摆放和绘制,手势分发
  • ParentDataWidget
    用在多孩子控件中,主要是获取父控件的ParentData
  • InheritedWidget
    数据共享,数据向下传递

Widget、Element、RenderObject关系?

Widget创建Element,子类RenderObjectWidget创建RenderObject
Element负责build,持有Widget的对象,绑定RenderObject
RenderObject负责渲染,Widget传递参数值,Element传递约束布局

关于Element 中 update方法的调用

update在第一次控件构建时不会调用,但第二次重新构建会调用(即setState时,控件内部的控件会执行update方法),update最后会执行build方法

关于调试

flutter源码不同于android源码,内部是可以修改的,加入标记和日志打印可以理清各过程(很方便)
错误打印日志非常全面,包含各流程(与java的一样)


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341