💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 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 最后标记当前节点需要被重绘 ## 总结 ### 核心思想 * 一层一层向下传递约束,直到叶子节点 * 子节点根据约束,确定自己的尺寸,然后把自己的尺寸向上传递