2、Widget、Element、RenderObjectnode
4、build 流程分析bash
8、composite 流程分析post
执行 Build 流程以后,接下来则是布局流程,即完成相关节点的大小,位置测量this
(1) drawFrameurl
void drawFrame() {
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}
复制代码
(2)flushLayout
void flushLayout() {
while (_nodesNeedingLayout.isNotEmpty) {
final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
if (node._needsLayout && node.owner == this)
node._layoutWithoutResize();
}
}
}
复制代码
这个方法中,实际上是遍历 _nodesNeedingLayout (须要从新布局的节点)集合, 分别调用 _layoutWithoutResize 方法 那 _nodesNeedingLayout 从何而来? 继续追踪,会发如今 markNeedsLayout 中 会有相关处理逻辑
(3)markNeedsLayout
void markNeedsLayout() {
if (_relayoutBoundary != this) {
// 绘制边界不是吱声,则向上遍历,直到找到最近 relayoutBoundary 为 true 的父元素
markParentNeedsLayout();
} else {
_needsLayout = true;
if (owner != null) {
// 绘制边界是自身,则将该节点加入待从新 layout 节点集合中
owner._nodesNeedingLayout.add(this);
}
}
}
复制代码
markNeedsLayout 什么时候调用呢? 答案是: updateRenderObject
以 RenderImage 为例, 当 image、width、height 属性变化时都会触发调用markNeedsLayout函数,以标记该 renderObject 须要从新 layout
// 设置图像url
set image(ui.Image value) {
if (_width == null || _height == null)
markNeedsLayout();
}
// 设置宽度
set width(double value) {
if (value == _width)
return;
_width = value;
markNeedsLayout();
}
// 设置高度
set height(double value) {
if (value == _height)
return;
_height = value;
markNeedsLayout();
}
(4)_layoutWithoutResize
void _layoutWithoutResize() {
performLayout()
}
复制代码
(5)performLayout
这个方法在 RenderObject 中只是声明了,具体实现是由具体子类去重写, 例如以 根节点 RenderView 为例, 其中的 performlayout 就调用了子类的layout 方法, 即该方法是为了完成子节点的布局
void performLayout() {
_size = configuration.size;
if (child != null)
child.layout(BoxConstraints.tight(_size));
}
复制代码
(6)layout
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 (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {
return;
}
_constraints = constraints;
_relayoutBoundary = relayoutBoundary;
if (sizedByParent) {
performResize();
}
RenderObject debugPreviousActiveLayout;
performLayout();
markNeedsSemanticsUpdate();
markNeedsPaint();
}
复制代码
layout 方法中,除了完成自身的布局外,还须要调用 performLayout 完成子节点的布局, 所以最终的调用栈为:layout() > performResize()/performLayout() > child.layout()
,如此递归完成整个UI的布局。
一个节点在界面上的显示须要两个条件: 大小 size 位置 offset 在 flutter 中,renderObject 节点的布局经历了一个 自上而下
和 自下而上
的一个递归过程,在 performLayout 中,父前节点会调用子节点的 layout 方法,同时将约束参数 constraints 传递给子节点,子节点布局完以后,能计算出自身的 size, 而后 自下而上分别将 size 传回的父节点
(1) constraint
用以控制子节点的最大和最小宽高,由父节点传递在布局时传递给子节点,子节点必须遵照父节点给定的限制条件,maxWidth, maxHeight, minWidth, minHeight
(2) sizedByParent
若是这个参数值是 true, 则意味着该节点的大小仅仅经过 parent 传给它的 constraints 就能够肯定了,与其自身的属性和子节点无关, 即其大小在 performResize() 中就肯定了,在后面的 performLayout() 方法中将不会再被修改了
(3) relayoutBoundary
布局边界,一个节点大小改变时,可能会影响父节点,这个参数是用来控制当子节大小点发生改变时,是否须要更新父节点的布局,若是这个属性值指向自身 , 则不须要通知父节点改变。 由layout 源码能够看到,这个值由如下几个参数决定 parentUsesSize 为 false sizedByParent 该值为true时,其节点大小由父节点所传的 constraint 决定 constraints.isTight parent 不是 RenderObject 以上的分析,咱们大体了解节点布局的大体流程、节点大小的设置原理、若是布局的话,还须要节点的位置,那位置信息保存在哪里呢?答案是: ParentData
(4) parentData
这个参数主要是用来保存节点的相关位置信息,例如位移 offset, TableCellParentData 中 x(所在的列)、y(所在的行)等,TextParentData 中 文字的缩放比例 scale 等。
ParentData 有不一样的实现类,具体可查看
api.flutter-io.cn/flutter/ren…
布局过程当中,存储位置相关信息又是如何更新的呢? 以 RenderListBody 为例:
void performLayout() {
double mainAxisExtent = 0.0; // 主轴初始位移
RenderBox child = firstChild;
switch (axisDirection) {
case AxisDirection.right:
final BoxConstraints innerConstraints = BoxConstraints.tightFor(height: constraints.maxHeight);
while (child != null) {
// 注释1: 子节点布局
child.layout(innerConstraints, parentUsesSize: true);
final ListBodyParentData childParentData = child.parentData;
// 注释2: 更新parentData 中 offset 值
childParentData.offset = Offset(mainAxisExtent, 0.0);
mainAxisExtent += child.size.width;
child = childParentData.nextSibling;
}
size = constraints.constrain(Size(mainAxisExtent, constraints.maxHeight));
break;
case AxisDirection.left:
...
case AxisDirection.down:
...
break;
case AxisDirection.up:
...
break;
}
}
复制代码
在以上代码中,注释1:在performLayout 函数中,先调用 childLayout 完成子节点的布局,完成子节点布局以后,各个节点的size 已经肯定,经过遍历设置各个节点 parentData 的 offset 值 即可以完成各个节点的位置信息。
如上图中蓝色部分,在 build 过程会涉及到 renderObject建立、更新、这两部分会触发 markNeedsLayout 函数 记录须要更新布局的 renderObject 节点,当 build 流程完成,调用 flushLayout 时,则会遍历这些节点,完成各个节点的大小计算,以及相关位置信息的更新