Flutter学习笔记27-Widget-Element-RenderObject

Flutter从创建到渲染的大体流程是:根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。可以认为Flutter的UI系统包含三棵树:Widget树、Element树、RenderObject树。Element树根据Widget树生成,而渲染树又依赖于Element树。如下图:

Flutter学习笔记27-Widget-Element-RenderObject_第1张图片

Widget

在Flutter中,Widget的功能是“描述一个UI元素的配置数据”,Widget其实并不是表示最终绘制在设备屏幕上的显示元素,而它只是描述显示元素的一个配置数据。
Flutter中真正代表屏幕上显示元素的类是Element,也就是说Widget只是描述Element的配置数据。Widget只是UI元素的一个配置数据,并且一个Widget可以对应多个Element。这是因为同一个Widget对象可以被添加到UI树的不同部分,而真正渲染时,UI树的每一个Element节点都会对应一个Widget对象。

  • Widget实际上就是Element的配置数据,Widget树实际上是一个配置树,而真正的UI渲染树是由Element构成;不过,由于Element是通过Widget生成的,所以它们之间有对应关系,在大多数场景,我们可以宽泛地认为Widget树就是指UI控件树或UI渲染树。
  • 一个Widget对象可以对应多个Element对象。根据同一份配置(Widget),可以创建多个实例(Element)。

Element

Element就是Widget在UI树具体位置的一个实例化对象,大多数Element只有唯一的renderObject,但还有一些Element会有多个子节点,如继承自RenderObjectElement的一些类,比如MultiChildRenderObjectElement。最终所有Element的RenderObject构成渲染树。
Element的生命周期如下:

  1. Framework 调用Widget.createElement创建一个Element实例,记为element
  2. 当有父Widget的配置数据改变时,同时其State.build返回的Widget结构与之前不同,此时就需要重新构建对应的Element树。为了进行Element复用,在Element重新构建前会先尝试是否可以复用旧树上相同位置的element,element节点在更新前都会调用其对应Widget的canUpdate方法,如果返回true,则复用旧Element,旧的Element会使用新Widget配置数据更新,反之则会创建一个新的Element。
  3. Widget.canUpdate主要是判断newWidget与oldWidget的runtimeTypekey是否同时相等,如果同时相等就返回true,否则就会返回false。根据这个原理,当需要强制更新一个Widget时,可以通过指定不同的Key来避免复用。
  4. 当有祖先Element决定要移除element 时(如Widget树结构发生了变化,导致element对应的Widget被移除),这时该祖先Element就会调用deactivateChild方法来移除它,移除后element.renderObject也会被从渲染树中移除,然后Framework会调用element.deactivate方法,这时element状态变为“inactive”状态。
  5. “inactive”态的element将不会再显示到屏幕。为了避免在一次动画执行过程中反复创建、移除某个特定element,“inactive”态的element在当前动画最后一帧结束前都会保留,如果在动画执行结束后它还未能重新变成“active”状态,Framework就会调用其unmount方法将其彻底移除,这时element的状态为defunct,它将永远不会再被插入到树中。
  6. 如果element要重新插入到Element树的其它位置,如element或element的祖先拥有一个GlobalKey(用于全局复用元素),那么Framework会先将element从现有位置移除,然后再调用其activate方法,并将其renderObject重新attach到渲染树。

BuildContext

StatelessWidgetStatefulWidgetbuild方法都会传一个BuildContext对象:

Widget build(BuildContext context) {}

BuildContext其是一个抽象接口类:

abstract class BuildContext {
    ...
}

build调用是发生在StatelessWidgetStatefulWidget对应的StatelessElementStatefulElementbuild方法中,StatelessElement:

class StatelessElement extends ComponentElement {
  ...
  @override
  Widget build() => widget.build(this);
  ...
}

build传递的参数是this,所以BuildContext就是StatelessElement。同样,StatefulWidgetcontextStatefulElement。但StatelessElementStatefulElement本身并没有实现BuildContext接口,继续跟踪代码,发现它们间接继承自Element类,Element类实现了BuildContext接口:

class Element extends DiagnosticableTree implements BuildContext {
    ...
}

所以BuildContext就是widget对应的Element,可以通过contextStatelessWidgetStatefulWidgetbuild方法中直接访问Element对象。

RenderObject

每个Element都对应一个RenderObject,可以通过 Element.renderObject来获取。RenderObject的主要职责是Layout和绘制,所有的RenderObject会组成一棵渲染树Render Tree。
RenderObject就是渲染树中的一个对象,它拥有一个parent和一个parentData插槽(slot),所谓插槽,就是指预留的一个接口或位置,这个接口和位置是由其它对象来接入或占据的,这个接口或位置在软件中通常用预留变量来表示,parentData正是一个预留变量,它由parent来赋值的,parent通常会通过子RenderObject的parentData存储一些和子元素相关的数据,如在Stack布局中,RenderStack就会将子元素的偏移数据存储在子元素的parentData中(具体可以查看Positioned实现)。
RenderObject类本身实现了一套基础的layout和绘制协议,Flutter提供了一个RenderBox类,它继承自RenderObject,布局坐标系统采用笛卡尔坐标系,这和Android和iOS原生坐标系是一致的,都是屏幕的top、left是原点,然后分宽高两个轴。RenderObject中有markNeedsLayoutperformLayoutmarkNeedsPaintpaint等方法完成Layout和绘制。

你可能感兴趣的:(Flutter学习笔记27-Widget-Element-RenderObject)