## drawFrame
~~~
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.
}
~~~
在engine回调`window`的`onDrawFrame()`函数,`onDrawFrame()`里的是buildScope是build阶段。接下来的函数`super.drawFrame()`里面的第一个调用`pipelineOwner.flushLayout()`就是layout阶段。
## pipelineOwner.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
1. 这里是按照在node tree中的深度顺序遍历\_nodesNeedingLayout
2. RenderObject的markNeedsLayout方法会把自己添加到\_nodesNeedingLayout
3. 最常见调用它的路径是,setState的时候把某个Widget的child改成别的Widget。也就是只要某个Widget的child结点改变或者其他操作导致重新布局时,就会触发调用markNeedsLayout。
~~~
void markNeedsLayout() {
if (_needsLayout) {
return;
}
if (_relayoutBoundary != this) {
markParentNeedsLayout(); // 会调用parent.markNeedsLayout
} else {
_needsLayout = true;
if (owner != null) {
owner._nodesNeedingLayout.add(this);
owner.requestVisualUpdate();
}
}
}
~~~
这个方法的处理逻辑就是:
1. 先判断relayoutBoundary是不是自己,如果是就把自己存到\_nodesNeedingLayout,等待下一次Vsync对自己重新布局layout;
2. 如果relayoutBoundary不是自己,那就向上找问parent要relayoutBoundary。所以这是个递归,直到找到relayoutBoundary为止。
那么这段代码作用就很明确了,那就是如果这个Widget有变动,它需要找到被它影响到的范围,而这个范围就是relayoutBoundary。然后需要对relayoutBoundary重新进行layout。
### _layoutWithoutResize
~~~
void _layoutWithoutResize() {
try {
performLayout();
markNeedsSemanticsUpdate();
} catch (e, stack) {
...
}
_needsLayout = false;
markNeedsPaint();
}
~~~
1. 函数`performLayout()`需要其子类自行实现。因为有各种各样的布局,就需要子类个性化的实现自己的布局逻辑。
## performLayout
### RenderConstrainedBox
`RenderPositionedBox` 的布局函数如下:
~~~
@override
void performLayout() {
if (child != null) {
child.layout(constraints.loosen(), parentUsesSize: true);
size = constraints.constrain(Size(shrinkWrapWidth ? child.size.width * (_widthFactor ?? 1.0) : double.infinity,
shrinkWrapHeight ? child.size.height * (_heightFactor ?? 1.0) : double.infinity));
alignChild();
}
}
~~~
### RenderDecoratedBox
~~~
@override
void performLayout() {
if (child != null) {
child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true);
size = child.size;
} else {
size = _additionalConstraints.enforce(constraints).constrain(Size.zero);
}
}
~~~
## layout
在`performLayout()`的实现中。当有孩子节点的时候,一般会调用`child.layout()`请求孩子节点做布局。调用时要传入对孩子节点的约束`constraints`,我们看一下`child.layout()`。这个函数在`RenderObject`类中:
~~~
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) {
try {
performResize();
} catch (e, stack) {
...
}
}
try {
performLayout();
markNeedsSemanticsUpdate();
} catch (e, stack) {
...
}
_needsLayout = false;
markNeedsPaint();
}
~~~
### 第一步:确定relayoutBoundary
4个条件:
1. `parentUsesSize`:父组件是否需要子组件的尺寸,这是调用时候的入参,默认为`false`。
2. `sizedByParent`:这是个`RenderObject`的属性,表示当前`RenderObject`的布局是否只受父`RenderObject`给与的约束影响。默认为`false`。子类如果需要的话可以返回`true`。比如`RenderErrorBox`。当我们的Flutter app出错的话,屏幕上显示出来的红底黄字的界面就是由它来渲染的。
3. `constraints.isTight`:代表约束是否是严格约束。也就是说是否只允许一个尺寸。
4. 最后一个条件是父亲节点是否是`RenderObject`。
在以上条件任一个满足时,`relayoutBoundary`就是自己,否则取父节点的`relayoutBoundary`。
### 第二步:尽量避免重新布局
如果当前节点不需要做重新布局,约束也没有变化,`relayoutBoundary`也没有变化就直接返回了。也就是说从这个节点开始,包括其下的子节点都不需要做重新布局了。这样就会有性能上的提升。
### 第三步 针对sizedByParent == true
如果`sizedByParent`为`true`,会调用`performResize()`。这个函数会仅仅根据约束来计算当前`RenderObject`的尺寸。当这个函数被调用以后,通常接下来的`performLayout()`函数里不能再更改尺寸了。
### 第四步 performLayout
`performLayout()`是大部分节点做布局的地方了。不同的`RenderObject`会有不同的实现。
### 第五步 markNeedsPaint
最后标记当前节点需要被重绘
## 总结
### 核心思想
* 一层一层向下传递约束,直到叶子节点
* 子节点根据约束,确定自己的尺寸,然后把自己的尺寸向上传递