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 時,則會遍歷這些節點,完成各個節點的大小計算,以及相關位置信息的更新