Flutter-布局的树结构分析

Flutter布局的树结构

flutter中有三棵树,分别是Widget树、Element树、RenderObject树。
其中Widget中只存放基本的配置信息,可以当作就是一个数据结构,而RenderObject是实际渲染使用的,layout和paint等操作都是RenderObject完成的。
Element则是存放上下文的,同时持有Widget和RenderObject,并且负责处理state的状态变化。

State的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4FJkc64w-1600399525671)(/images/state_lifecycle.jpg)]

setState

State.setState() -> _element.markNeedsBuild()

setState就是将该widget标记为dirty,然后会在下一帧进行rebuild

从创建到渲染的大体流程是:根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。
他们的依赖关系是:Element树根据Widget树生成,而渲染树又依赖于Element树

Element的生命周期

1、Framework 调用Widget.createElement创建一个Element实例,记为element
2、Framework 调用element.mount(parentElement,newSlot),mount方法中首先调用element所对应Widget的createRenderObject方法创建与element相关联的RenderObject对象,然后调用element.attachRenderObject方法将element.renderObject添加到渲染树中插槽指定的位置(这一步不是必须的,一般发生在Element树结构发生变化时才需要重新attach)。插入到渲染树后的element就处于**“active”状态**,处于“active”状态后就可以显示在屏幕上了(可以隐藏)。
3、当有父Widget的配置数据改变时,同时其State.build返回的Widget结构与之前不同,此时就需要重新构建对应的Element树。为了进行Element复用,在Element重新构建前会先尝试是否可以复用旧树上相同位置的element,element节点在更新前都会调用其对应Widget的canUpdate方法,如果返回true,则复用旧Element,旧的Element会使用新Widget配置数据更新,反之则会创建一个新的Element。Widget.canUpdate主要是判断newWidget与oldWidget的runtimeType和key是否同时相等,如果同时相等就返回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到渲染树。

动画的过程:
1、deactivate方法从树中移除element会变成inactive状态
2、在动画结束时element的状态没有变成active状态
3、unmount方法彻底删除element,element的状态变为defunct,再也不会被插入树中
4、activate方法会将element的RenderObject重新attach到渲染树中

BuildContext其实就是Element,Element就是上下文

RenderObject

layout

layout方法需要传入两个参数,第一个为constraints,即 父节点对子节点大小的限制,该值根据父节点的布局逻辑确定。另外一个参数是 parentUsesSize,该值用于确定 relayoutBoundary,该参数表示子节点布局变化是否影响父节点,如果为true,当子节点布局发生变化时父节点都会标记为需要重新布局,如果为false,则子节点布局发生变化后不会影响父节点。

BoxConstraints就是用来限制子节点最大最小尺寸的。

void layout(Constraints constraints, { bool parentUsesSize = false }) {
   ...
   RenderObject relayoutBoundary; 
    if (!parentUsesSize || sizedByParent || constraints.isTight 
        || parent is! RenderObject) {
      relayoutBoundary = this;
    } else {
      final RenderObject parent = this.parent;
      relayoutBoundary = parent._relayoutBoundary;
    }
    ...
    if (sizedByParent) {
        performResize();
    }
    performLayout();
    ...
}

relayoutBoundary是用来确认重新布局的界限的,例如使用markNeedsBuild将一个Element标记成dirty的,它会去寻找父节点是relayoutBoundary的RenderObject,将其标记为dirty,其父节点就不需要rebuild了

performResize 和 performLayout
sizedByParent如果子节点是根据父节点的约束确定size的就会执行performResize,performLayout将不会更新其size;performLayout中会将自己和其子节点都进行布局处理

draw

ParentData

由父节点给子节点设置ParentData,包括offset偏移值及一些其他信息。

RenderObject可以通过paint()方法来完成具体绘制逻辑,然后使用Canvas API实现具体绘制,由父节点往子节点进行依次绘制。

RepaintBoundary

与RelayoutBoundary不同的是,这个绘制边界需要由开发者通过RepaintBoundary 组件自己指定。独立绘制是通过在不同的layer(层)上绘制的。所以,很明显,正确使用isRepaintBoundary属性可以提高绘制效率,避免不必要的重绘。

深入了解Flutter界面开发

Flutter - 从树开始了解Flutter

More info: CSDN

你可能感兴趣的:(flutter,flutter)