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 内部世界的大门。


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


相关文章
|
19天前
|
存储 开发框架 JavaScript
深入探讨Flutter中动态UI构建的原理、方法以及数据驱动视图的实现技巧
【6月更文挑战第11天】Flutter是高效的跨平台移动开发框架,以其热重载、高性能渲染和丰富组件库著称。本文探讨了Flutter中动态UI构建原理与数据驱动视图的实现。动态UI基于Widget树模型,状态变化触发UI更新。状态管理是关键,Flutter提供StatefulWidget、Provider、Redux等方式。使用ListView等可滚动组件和StreamBuilder等流式组件实现数据驱动视图的自动更新。响应式布局确保UI在不同设备上的适应性。Flutter为开发者构建动态、用户友好的界面提供了强大支持。
33 2
|
2天前
|
监控 Serverless 持续交付
阿里云云效产品使用问题之如何让流水线支持构建 flutter web 应用到 OSS
云效作为一款全面覆盖研发全生命周期管理的云端效能平台,致力于帮助企业实现高效协同、敏捷研发和持续交付。本合集收集整理了用户在使用云效过程中遇到的常见问题,问题涉及项目创建与管理、需求规划与迭代、代码托管与版本控制、自动化测试、持续集成与发布等方面。
|
1天前
|
开发框架 移动开发 Android开发
构建高效移动应用:探索Flutter开发框架
【6月更文挑战第28天】随着移动设备的普及,用户对移动应用的需求日益增长。开发者面临着在众多平台间提供无缝体验的挑战。本文深入探讨了Flutter框架如何通过其跨平台特性、热重载功能以及丰富的组件库简化移动应用的开发流程,同时确保高性能和优雅的用户界面设计。
8 2
|
12天前
|
移动开发 Dart 前端开发
深度分析:React Native、Flutter、UniApp、Taro、Vue的差异
深度分析:React Native、Flutter、UniApp、Taro、Vue的差异
48 6
|
19天前
|
开发框架 Dart JavaScript
深入探讨Flutter中的Web支持功能,以及如何利用Flutter构建跨平台Web应用的最佳实践
【6月更文挑战第11天】Flutter,Google的开源跨平台框架,已延伸至Web支持,让开发者能用同一代码库构建移动和Web应用。Flutter Web基于Dart转JavaScript,利用WebAssembly和JavaScript在Web上运行。构建Web应用最佳实践包括选择合适项目、优化性能、进行兼容性测试和利用Flutter的声明式UI、热重载等优势。尽管性能挑战存在,Flutter Web为跨平台开发提供了更多机会和潜力。
41 1
|
2月前
|
Dart 安全 Android开发
构建未来:基于Flutter的跨平台移动应用开发
【5月更文挑战第28天】 在当今多元化的移动应用市场中,开发人员面临着为不同操作系统创建单独应用程序的挑战。这不仅增加了开发成本,也延长了上市时间。本文将探讨使用Google推出的开源UI工具包——Flutter进行高效、一致且高性能的跨平台移动应用开发。我们将深入分析Flutter框架的核心特性,并通过一个实际案例来阐述如何利用Dart语言和热重载功能快速迭代原型设计。文章的目标是向读者展示Flutter不仅能够缩减开发周期,还能提供接近原生应用的用户体验。
|
2月前
|
开发框架 Dart 前端开发
【Flutter前端技术开发专栏】Flutter中的Web支持:构建跨平台Web应用
【4月更文挑战第30天】Flutter,Google的开源跨平台框架,已延伸至Web领域,让开发者能用同一代码库构建移动和Web应用。Flutter Web通过将Dart代码编译成JavaScript和WASM运行在Web上。尽管性能可能不及原生Web应用,但适合交互性强、UI复杂的应用。开发者应关注性能优化、兼容性测试,并利用Flutter的声明式UI、热重载等优势。随着其发展,Flutter Web为跨平台开发带来更多潜力。
【Flutter前端技术开发专栏】Flutter中的Web支持:构建跨平台Web应用
|
2月前
|
开发框架 前端开发 JavaScript
【Flutter前端技术开发专栏】Flutter中的动态UI构建与数据驱动视图
【4月更文挑战第30天】Flutter是一款高效跨平台移动开发框架,以其热重载、高性能渲染和丰富组件库著称,简化了动态UI和数据驱动视图的实现。本文深入讨论了动态UI构建原理,包括基于Widget树模型的UI更新和状态管理,如使用StatefulWidget和数据流库(如Provider、Redux)。此外,文中还介绍了实现技巧,如使用ListView等可滚动组件、StreamBuilder进行数据流驱动的UI更新,以及应用响应式布局以适应不同设备。Flutter为开发者提供了构建高效动态界面的强大工具。
【Flutter前端技术开发专栏】Flutter中的动态UI构建与数据驱动视图
|
2月前
|
开发框架 前端开发 JavaScript
【专栏】对比分析两种流行的跨平台开发框架——Flutter和React Native,探讨它们的优势、劣势以及适用场景
【4月更文挑战第27天】本文对比分析了Flutter和React Native两大跨平台移动开发框架。Flutter,由Google推出,以其接近原生的性能、快速启动和流畅滚动受青睐,适合高性能和高度定制的项目。React Native,Facebook维护,依赖JavaScript,虽性能受限,但热重载优势和丰富第三方库使其适合快速迭代的项目。两者都在拓展多平台应用,Flutter在桌面和Web,React Native在Windows。选择框架需考虑项目需求、团队技能和性能效率平衡。
|
2月前
|
Dart IDE Android开发
构建未来:基于Flutter的跨平台移动应用开发探索
【5月更文挑战第18天】在移动应用开发的世界中,跨平台技术正迅速成为一股不可忽视的力量。特别是Google推出的Flutter框架以其高效的性能和一套代码适应多平台的特性受到开发者的青睐。本文旨在探讨Flutter框架的核心优势、开发流程以及其在安卓与iOS平台上的应用实践,为开发者提供一个全面的技术分析视角,并展望Flutter在未来移动开发领域中的潜在发展。