## 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
最后標記當前節點需要被重繪
## 總結
### 核心思想
* 一層一層向下傳遞約束,直到葉子節點
* 子節點根據約束,確定自己的尺寸,然后把自己的尺寸向上傳遞