Flutter | 三棵树构建流程分析(下)

简介: Flutter | 三棵树构建流程分析(下)

我们大致分析一下


首先是调用到 MultiChildRenderObjectElement 的 mount 方法中,先将传入的 parent 插入到树中,

接着在 RenderObjectElement 中 创建了一个 renderObject 并添加到渲染树中插槽的指定位置

最后回到 MultiChildRenderObjectElement 中遍历所有的 child,并且调用 inflateWidget 创建 child并插入到指定的插槽中。


StatefulElement


相比于 StatelessWidget ,StatefulWidget 中多了一个 State,在 StatefulWidget 中会通过 createState 来创建一个 State,如下所示


abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key? key }) : super(key: key);
  @override
  StatefulElement createElement() => StatefulElement(this);
  @protected
  @factory
  State createState(); // ignore: no_logic_in_create_state, this is the original sin
}


通过上面代码可以看出 StatefulWidget 对应的 Element 正是 StatefulElement。


class StatefulElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    assert(state._element == null);
    state._element = this;
    state._widget = widget;
  }
  State<StatefulWidget> get state => _state!;
  State<StatefulWidget>? _state;
  @override
  Widget build() => state.build(this);
}


在 StatefulElement 中通过调用 widget.createState 获取到了 state 对象


StatefulElement 在创建完成后,framework 就会调用 StatelessElement 中的 mount 方法了。


和 StatelessElement 不同的是:


Statelesselement 中是通过调用 widget.build(this) 方法

StatefulElement 中是通过调用 state.build(this) 方法


总结


通过上面的分析,我们知道了 Element 的生命周期以及它的调用过程。并且如果你仔细观察上面举例的三个 Element 就会发现,上面的这三种 Element 可以分为两类,分别是组合类和绘制类。


组合类一般都继承自 StatelessElement 或者 StatefulElement,它们都属于组合类,并不参与绘制,内部嵌套了多层 Widget,查看它们的 mount 方法,就会发现其中并没有创建 renderObject 并添加到渲染树。例如 StatelessWidget ,StatefulWidget,Text,Container ,Image 等。

绘制类的特征顾名思义就是参加绘制了,在mount 方法中,会有创建 renderObject 并 attachRenderObject 渲染树中。如 Column ,SizedBox,Transform 等。

其实还有一种类型,这里借用大佬的一张图来看一下:


image.png


代理类对应的 element 是 ProxyElement。


RenderObject


在上面我们提到过每一个 Element 都对应一个 RenderObject,我们可以通过 Element.renderObject 来获取。并且 RenderObject 的主要职责是 Layout 绘制,所有的 RenderObject 会组成一颗渲染树 Render Tree。


通过上面的分析,我们知道树的核心就在 mount 方法中,我们直接查看 RenderObjectElement.mount() 方法


@override
void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  _renderObject = widget.createRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;
}


在执行完 super.mount 之后 (将 parent 插入到 element 树中),执行了 attachRenderObject 方法。


@override
void attachRenderObject(Object? newSlot) {
  assert(_ancestorRenderObjectElement == null);
  _slot = newSlot;
  //查询当前最近的 RenderObject 对象  
  _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
  // 将当前节点的 renderObject 对象插入到上面找的的 RenderObject下面  
  _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
  //......... 
}
RenderObjectElement? _findAncestorRenderObjectElement() {
   Element? ancestor = _parent;
   while (ancestor != null && ancestor is! RenderObjectElement)
     ancestor = ancestor._parent;
   return ancestor as RenderObjectElement?;
 }


上面代码也不难理解,就是一个死循环,退出的添加就是 ancestor 父节点为空和父节点是 RenderObjectElement 。说明这个方法会找到离当前节点最近的一个 RenderObjectElement 对象,然后调用 insertRenderObjectChild 方法,这个方法是一个抽象方法,我们查看两个类里面的重写逻辑


SingleChildRenderObjectElement


void insertRenderObjectChild(RenderObject child, Object? slot) {
  final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject as RenderObjectWithChildMixin<RenderObject>;
  renderObject.child = child;
}


在上面代码中,找到了当前 RenderObjectElement 的 renderObject ,并且将我们传入的 child 给了 renderObject 的 child 。所以这样就将传入的 child 挂在了 RenderObject 树上。


MultiChildRenderObjectElement


@override
void insertRenderObjectChild(RenderObject child, IndexedSlot<Element?> slot) {
  final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject = this.renderObject;
  assert(renderObject.debugValidateChild(child));
  renderObject.insert(child, after: slot.value?.renderObject);
  assert(renderObject == this.renderObject);
}


上面代码中,找到 renderObject 之后赋值给了 ContainerRenderObjectMixin> 这个类,我们来看一下这个类


/// Generic mixin for render objects with a list of children.
/// 具有子项列表的渲染对象的通用混合
/// Provides a child model for a render object subclass that has a doubly-linked
/// list of children.
/// 为具有双向链接的子项列表的渲染对象子类提供子模型
mixin ContainerRenderObjectMixin<ChildType extends RenderObject, ParentDataType extends ContainerParentDataMixin<ChildType>> on RenderObject {
}


泛型 mixin 用于渲染一组子对象的对象。看到这里我们可以知道 MultiChildRenderObjectElement 是可以有子列表的。


通过上面的注释可以看出一个重点,双向链表。所以 MultiChildRenderObjectElement 的子节点通过双向链表连接。上面 insert 最终会调用到 _insertIntoChildList 方法中,如下:


ChildType? _firstChild;
ChildType? _lastChild;
void _insertIntoChildList(ChildType child, { ChildType? after }) {
  final ParentDataType childParentData = child.parentData! as ParentDataType;
  _childCount += 1;
  assert(_childCount > 0);
  if (after == null) {
    // after 为 null,则插入到 _firstChild 中
    childParentData.nextSibling = _firstChild;
    if (_firstChild != null) {
      final ParentDataType _firstChildParentData = _firstChild!.parentData! as ParentDataType;
      _firstChildParentData.previousSibling = child;
    }
    _firstChild = child;
    _lastChild ??= child;
  } else {
    final ParentDataType afterParentData = after.parentData! as ParentDataType;
    if (afterParentData.nextSibling == null) {
      // insert at the end (_lastChild); we'll end up with two or more children 
      // 将 child 插入到末尾  
      assert(after == _lastChild);
      childParentData.previousSibling = after;
      afterParentData.nextSibling = child;
      _lastChild = child;
    } else {
      // insert in the middle; we'll end up with three or more children
      // 插入到中间
      childParentData.nextSibling = afterParentData.nextSibling;
      childParentData.previousSibling = after;
      // set up links from siblings to child
      final ParentDataType childPreviousSiblingParentData = childParentData.previousSibling!.parentData! as ParentDataType;
      final ParentDataType childNextSiblingParentData = childParentData.nextSibling!.parentData! as ParentDataType;
      childPreviousSiblingParentData.nextSibling = child;
      childNextSiblingParentData.previousSibling = child;
      assert(afterParentData.nextSibling == child);
    }
  }
}


根据上面的注释我们可以将其分为三部分,1,在 after 为 null 的时候将 child 插入到第一个节点,2,将 child 插入到 末端,3,将 child 插入到中间。


我们以一个例子为例:


Column(
  children: [
     SizedBox(.....),
     Text(data),
      Text(data),
      Text(data),
   ],
)


第一个 Stack 向上找到 Column( RenderObjectElement ) 之后, 调用这个方法,目前 after 为 null,则 _firstchild 就是 SizedBox( SizedBox 对应的 renderObject 是 RenderConstrainedBox)


第二个是 Text,我们知道 Text 是组合类型的,所以他不会被挂载到 树中,通过查询源码可以看出最终的 text 用的是 RichText。RichText 向上查找到 Column( RenderObjectElement ) 之后 ,调用这个方法,传入了两个参数,第一个 child 是 RichText 对应的 RenderParagraph ,第二个 after 是 SizedBox 对应的 RenderConstrainedBox 。依照上面逻辑,执行下面的代码


final ParentDataType afterParentData = after.parentData! as ParentDataType;
if (afterParentData.nextSibling == null) {
  // insert at the end (_lastChild); we'll end up with two or more children
  assert(after == _lastChild);
  childParentData.previousSibling = after;
  afterParentData.nextSibling = child;
  _lastChild = child;
}


将 child 的 childParentData.previousSibling 指向第一个节点,将第一个接的的 afterParentData.nextSibling 指向 child,最后让 _lastchild 执行 child。


后面也是如此,当流程结束后就可以得到一个 RenderTree。


总结


本文主要介绍了三棵树的构建过程以及 elemnt 的生命周期,这些虽然我们在开发过程中用的比较少,但是却是通向 flutter 内部世界的大门。


其实在写这篇文章的时候也是一知半解,通过不断的查看源码,翻看博客等才慢慢的有些了解。并且在最后产出了这篇文章,可能文章中会有一些错误,如果你看到的话麻烦在下面提出来,感谢!!


相关文章
|
10天前
|
开发工具 Android开发 iOS开发
零基础构建即时通讯开源项目OpenIM移动端-Flutter篇
OpenIM 为开发者提供开源即时通讯 SDK,作为 Twilio、Sendbird 等云服务的替代方案。借助 OpenIM,开发者可以构建安全可靠的即时通讯应用,如 WeChat、Zoom、Slack 等。 本仓库基于开源版 OpenIM SDK 开发,提供了一款基于 Flutter 的即时通讯应用。您可以使用此应用程序作为 OpenIM SDK 的参考实现。 开发环境 在开始开发之前,请确保您的系统已安装以下软件: 操作系统:macOS 14.6 或更高版本 Flutter:版本 3.24.5(根据官网步骤进行安装) Git:用于代码版本控制 同时,您需要确保已经部署了最
58 9
|
11天前
|
前端开发 安全 开发工具
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
141 90
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
14天前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
37 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
16天前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
121 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
24天前
|
Dart 前端开发 容器
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
75 18
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
26天前
|
缓存 前端开发 IDE
【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
27 0
【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
27天前
|
Dart 前端开发
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
116 75
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
29天前
|
缓存 前端开发 Android开发
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
79 12
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
|
1月前
|
前端开发 Java 开发工具
【03】完整flutter的APP打包流程-以apk设置图标-包名-签名-APP名-打包流程为例—-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈 章节内容【03】
【03】完整flutter的APP打包流程-以apk设置图标-包名-签名-APP名-打包流程为例—-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈 章节内容【03】
81 18
【03】完整flutter的APP打包流程-以apk设置图标-包名-签名-APP名-打包流程为例—-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈 章节内容【03】
|
1月前
|
Dart 前端开发 Android开发
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
36 1
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈

热门文章

最新文章

  • 1
    【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 2
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 3
    【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 4
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
  • 5
    【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 6
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
  • 7
    零基础构建即时通讯开源项目OpenIM移动端-Flutter篇
  • 8
    flutter3-dart3-dymall原创仿抖音(直播+短视频+聊天)商城app系统模板
  • 9
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 10
    【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 1
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
    14
  • 2
    零基础构建即时通讯开源项目OpenIM移动端-Flutter篇
    58
  • 3
    flutter3-dart3-dymall原创仿抖音(直播+短视频+聊天)商城app系统模板
    38
  • 4
    【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    141
  • 5
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    37
  • 6
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
    73
  • 7
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    121
  • 8
    【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
    75
  • 9
    flutter开发中Use ‘const’ with the constructor to improve performance. Try adding the ‘const’ keyword to the constructor invocation.报错如何解决-优雅草卓伊凡
    18
  • 10
    【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
    27