环境: flutter sdk v1.7.8+hotfix.4@stable

对于界面开发,通常的视图树都是通过视图对象持有父节点与子节点列表而建立的双向节点树,如android中的View(子节点抽象)与ViewParent(父节点抽象,ViewGroup是其实现体),ViewGroup显式的持有了View类型的对象数组,通过各种dispatchXXX的方法将事件/操作/更新传递给子节点;但在flutter中似乎有些不同,Widget(视图控件抽象)没有区分父子关系,关键是Widget抽象里根本没有代表子节点与父节点的成员!它最关键的方法仅为createElement,那个extends DiagnosticableTree看上去很可疑,但只要细察下代码,基本上是为了方便调试而打印信息的。所以虽然可以在各种文章和代码中看到树的指称,但需要首先搞清这个树究竟指的是什么树。

树的含义

当然是Element树!虽然对于熟悉以往界面开发的人来说这个结论有点让人狐疑,但我们应该明确的得到肯定:就是这样,因为从任意一个控件抽象Widget出发,无法到达Widget根节点或者任何Widget子节点,也就是无法实施遍历操作,当然也就不是树形数据结构了。对于Web开发的人来说比较容易接受,经常在涉及Web的开发谈到Element,android的开发现在需要习惯这种指称,默认的树指的就是Element树,否则理解就容易产生歧义,同时之前文章所说的Widget树这种说法是错误的,因为根本就没有Widget树!

如前文所述像RenderObjectToWidgetAdapter这样的Widget不就显式的持有了一个Widget作为child成员吗?的确,但这样的持有是具体类子类的持有,还是无法通过访问成员再访问到它的子节点,这个联系根本就是中断的。

所以建树就是建立Element树,访问Widget也只能通过Element间接访问:在Element定义中可以看到它直接持有了一个Widget,访问到了Element也就访问到了Widget,这是从android转过来的开发人员需要反复铭记的一点。Element有一个_parent作为其成员,因此可以上溯到根节点的Widget,然而令人困惑的是Element并没有Element数组或者列表来代表子节点!那Element是如何访问子节点的?

遍历子节点

基类Element并没有直接持有数组或者列表来访问子节点,而是通过visitChildren的空实现体方法,方法参数(ElementVisitor)本身是一个方法(typedef ElementVisitor = void Function(Element element); framework.dart:1794)。

这不就是个访问者模式吗,然而为什么要这么搞?这么做的意图是希望完全由Element子类型来决定访问Element子节点的顺序,为遍历操作提供更大的灵活性,子节点的持有还是需要的,只不过由Element子类型具体实现。这是可以想到的,显然,如果我们在基类型持有了子节点,那遍历子节点就有了默认顺序。譬如android中的ViewGroup, 从头到尾的子视图列表顺序代表了由下到上的层次关系(ZOrder),但不得不再提供类似getChildDrawingOrder方法来让子类型有改变访问顺序的机会。

遍历形式从直接持有变成方法传递,这样做也是有缺点和风险的,那就是可能在运行期动态的改变访问子节点的顺序而造成视图数据的紊乱!所以在这个方法上也有明确的注释说明访问顺序保持一致的重要性:

/// There is no guaranteed order in which the children will be visited, though
/// it should be consistent over time.

在建立树的过程中也不能调用这方法,因为访问的可能是旧的子节点或者子节点还没有完全建立。这样看来直接持有Element子节点未必就不好。

建立树的过程

Element对象是如何一步步构建成树形结构的?虽然在Element代码定义上有一些注释可以参考建树的关键步骤,但最好还是从入口调用分析来看:

WidgetsBinding.attachRootWidgetRenderObjectToWidgetAdapter.attachToRenderTreeBuildOwner.buildScopeRenderObjectToWidgetElement.mountRootRenderObjectElement.mount(null, null)RenderObjectElement.mountElement.mountRenderObjectWidget.createRenderObject => RenderObjectToWidgetAdapter.createRenderObjectRenderObjectToWidgetElement._rebuildElement.updateChildElement.inflateWidgetWidget.createElement => MyAppElement.mount

这里涉及了一大坨Element类型及其方法,有些是自有方法,有些是覆盖方法,有些是基类方法,这个时候只能一步步分析,避免混乱。

RenderObjectToWidgetElementRenderObjectToWidgetAdapter这个Widget具体创建的Element类型,显式的调用了mount方法,并且传入的参数均为(null, null),前面的文章已说明RenderObjectToWidgetElement是真正的Element根节点。关键是它是如何串连起其它Element对象的?

由以上调用序列可知RenderObjectToWidgetElement.mount最终调用了Element.moutElement.mout其实就是建立指向关系,但它是根节点,不用再指向父节点,只需要关注其子节点创建,再看是如何关联子节点的。RenderObjectToWidgetElement有一个显式的成员_child, 是一个Element类型,发现其是在RenderObjectToWidgetElement._rebuild中被赋值的,而_rebuild又是在RenderObjectToWidgetElement.mount的实现体中被调用,这样走到了一个关键方法Element.updateChild,从其注释就可以看出来:

This method is the core of the widgets system.

通过两个重要参数为null与否,Element.updateChild区分了4种具有不同含义的操作,当前只需关注child != null && newWidget != null这种情况,从其注释看这正是创建子节点的途径!细分的调用序列如下:

Element.updateChildElement.inflateWidgetWidget.createElement => MyAppElement.mount

针对child != null && newWidget != null这种情况Element.updateChild最终调用的是Element.inflateWidget,注意这个名称有误导性,从代码可知当前Element没有对Widget有任何操作,只是调用了Widget.createElement, 而这个Widget对象是从外部传入的,不是当前Element自己持有的!具体的,这个Widget对象应该是当前Element关联的Widget对象的子对象(widget.childwidgets/binding.dart:939),对应的正是我们自定义的MyApp!

所以新创建的子Element是由子Widget创建,接着又调用了子Element的mount方法,传入的parent参数是this(newChild.mount(this, newSlot); framework.dart:3084),即将当前Element作为父节点与新建节点Element关联,这个mount非常形象的表现了一个新建节点挂在一个即有节点之上的操作,于是子节点的mount继续以上过程直至建立最终的节点。

如此看来,flutter的Element更像是一个衣物挂钩,它建立的树形结构更像前向单链表网,而钩子正是Element._parent

再看Widget关联

最开始说Widget并不持有子Widget,那么Element在mount的时候当前Widget又是如何提供子Widget来创建子Element的呢?

答案是还是要看当前Element具体操作mount的方式。譬如我们的根ElementRenderObjectToWidgetElement直接用了自身持有的根WidgetRenderObjectToWidgetAdapter持有的child来关联了我们传入的MyApp作为子Widget。

再譬如一个比较重要的Element类型ComponentElement:它是在mount的时候调用了一个自身的抽象方法Widget build() (framework.dart:3950), 这里返回的Widget对象正是当前Element需要创建的子Widget。而ComponentElement有两个最重要的实现类覆盖了Widget build() 方法:StatelessElement是通过持有的StatelessWidget对象再去创建一个子Widget对象;StatefulElement是通过持有的StatefulWidget对象创建的State<StatefulWidget>(framework.dart:3989)再去创建子Widget的。我们的MyApp再去创建它的子Widget时就是通过此类方式,因为MyApp是一个StatelessWidget对象,MyApp创建的Element是StatelessElement类型。

再譬如RenderObjectElementmount时还创建了RenderObject,并且关联父RenderObject,而这个父RenderObject未必是父Element关联的RenderObject(_findAncestorRenderObjectElementframework.dart:4950);

所以大部分Widget的父子关系并不是持有关系而是创建关系,并且是在Element.mount的时机创建的,创建后也并不持有!

结论

建立Element树最重要的操作就是Element.mount

每一种具体类型的Element,实现了如何将当前Element挂接(mount)到父节点上的操作;这个挂接操作除了与父Element建立指向关系外,还规定了当前Element的一些其它属性的创建时机和操作。

创建一个Element最重要的操作就是Element.updateChild

更具体的是Element.inflateWidget方法;通过创建子Widget方式的不同,区分了两大类Element和Widget: (StatelessElement, StatelessWidget)和(StatefulElement, StatefulWidget)

所谓的Element树更像是前向单链表网,单链表有共同的表头。

父类Element不持有Element子节点,而是通过Element.visitChildren把遍历操作交给具体的Element子类型来实现。

但是RenderObject却像普通的单链表,因为通过mixin RenderObjectWithChildMixin<RenderObject>提供的child, RenderObject能够直接遍历子节点。

转载于:https://www.cnblogs.com/lindeer/p/11340198.html

flutter: 建树流程相关推荐

  1. 关于Flutter初始化流程,我必须告诉你的是...

    作者:闲鱼技术-然道 1. 引言 最近在做性能优化的时候发现,在混合栈开发中,第一次启动Flutter页面的耗时总会是第二次启动Flutter页面耗时的两倍左右,这样给人感觉很不好.分析发现第一次启动 ...

  2. 深入分析 Flutter 初始化流程

    在调研 Flutter 动态化方案的时候,需要了解 Flutter 加载 dart 产物的流程,阅读了一部分源码,顺便也读了初始化相关的代码.于是梳理了一遍 Flutter 的初始化流程 flutte ...

  3. 重磅! flutter视图局部更新

    新建一个flutter工程, 以flutter框架给我们自动生成的代码为例, 当我们点击按钮更新记数_counter时,最终是通过调用State<T>.setState来更新视图的: se ...

  4. 革命性移动端开发框架-Flutter时间简史

    说到Flutter,可能很多同学都会将它和这几个词关联起来:新兴的.移动端.动态化.跨平台.开发框架. 从去年开始Flutter的热度在不断地上升,那么它对很多同学造成了一个误区:认为Flutter是 ...

  5. vbn中使用的3种流程控制结构是_细菌进化树构建:从模式种序列下载到构建系统发育树一键搞定...

    细菌进化树 • 构 建 细菌进化树构建:从模式种序列下载到构建系统发育树一键搞定 对于细菌新种或者新属的发现,总是那么让人期待,但是当我们批量获得16S序列后,逐一对这些尚不知分类地位的序列进行比对并 ...

  6. Flutter如何与Native(Android)进行交互

    目录 前言 BasicMessageChannel Android端- (1)不使用engine cache预热 (2)使用engine cache预热 Flutter端- MethodChannel ...

  7. 当 Flutter 遇见 Web,会有怎样的秘密?

    作者:haigecao,腾讯 CSIG Web 开发工程师 在线教育团队(简称:OED)已经将 Flutter 这样技术在业务中落地了,做为 IMWeb 前端团队的我们也要进行一些尝试.本文从前端角度 ...

  8. Flutter 核心原理与混合开发模式

    作者:airingdeng,腾讯QQ前端开发工程师 本文将从 Flutter 原理出发,详细介绍 Flutter 的绘制原理,借由此来对比三种跨端方案:之后再进入第三篇章 Flutter 混合开发模式 ...

  9. Flutter图像绘制原理深入分析

    题记 -- 执剑天涯,从你的点滴积累开始,所及之处,必精益求精,即是折腾每一天. ** 你可能需要 CSDN 网易云课堂教程 掘金 EDU学院教程 知乎 Flutter系列文章 本文章将讲述 CPU. ...

最新文章

  1. 清华大学AMiner发布计算机科学会议(期刊)影响力排名
  2. Swift SB 容器 Container View使用
  3. OpenHarmony的多内核
  4. Java面试题:如何将字符串反转?(翻转字符串)
  5. ios 关于自定义navigationItem,实现右侧多个按钮
  6. Java实现分类文件拷贝2
  7. PDMS Pipeline Tool 教程(四):目录树顺序检查
  8. c语言实用程序6,C语言实用程序设计100例流程图
  9. adb发送什么命令能在手机屏幕弹窗显示_如何通过命令给手机刷机
  10. 03_D-H参数表的建立
  11. ArchLinux-KDE桌面美化
  12. java实现火车票查询_java抓取12306信息实现火车余票查询示例
  13. 计算机水冷散热器原理,水冷散热器原理和作用是什么
  14. CSS 权威指南 读书笔记(五)
  15. 调用JavaAPI发送传真
  16. Hbuilder X APP开发 iPhoneX以上型号屏幕适应问题
  17. 小人数字时钟安卓版本APP
  18. 企业技术中台架构全景图(多图)
  19. 在Linux系统下实现进程,在Linux2.6内核下实现进程隐藏
  20. Java se:网络编程

热门文章

  1. java alarm api_JAVA抽象类及接口使用方法解析
  2. Tokenizers: How machines read
  3. MySQL Date and Time Types(日期和时间格式)
  4. 4.10 风格代价函数
  5. 1.1 为什么是 ML 策略
  6. mysql中怎么表示100美元_MySQL 事物,美国服务器
  7. python color属性_模块“cv2.cv2”没有“COLOR”属性“BGR2GREY”
  8. 关于http协议中的服务器状态情况
  9. Linux单用户模式(修改密码、运行级别)方法详解
  10. Redis学习总结(12)——Redis常见面试题再总结