新RenderObject的layout和paint
由以下RenderObject的attach方法源码可知,新RenderObject在attach的时候会被标记为需要layout和paint
//class RenderObject
@override
void attach(PipelineOwner owner) {
super.attach(owner);
if (_needsLayout && _relayoutBoundary != null) {
_needsLayout = false;
markNeedsLayout();
}
...
if (_needsPaint && _layer != null) {
_needsPaint = false;
markNeedsPaint();
}
...
}
flutter_layout.png
flutter_paint.png
layout流程
- markNeedsLayout : 是relayoutBoundary才会owner!._nodesNeedingLayout.add(this),否则尝试markParentNeedsLayout
- flushLayout : 该方法中PipelineOwner对_nodesNeedingLayout中的RenderObject进行layout操作,
- performLayout :RenderObject的该方法内会调用child的layout方法,layout方法内会调用markNeedsPaint方法,如果RenderObject是repaintBoundary,则添加到owner!._nodesNeedingPaint,如果不是repaintBoundary,则调起parent的markNeedsPaint方法。
paint流程
- layout流程中已经将是repaintBoundary且需要repaint的RenderObject添加到owner!._nodesNeedingPaint,其他情况也可能调起markNeedsPaint将RenderObject添加到owner!._nodesNeedingPaint,不一定是layout过程中。
- flushPaint :主要操作如下
- 如果RenderObject的_layer为空,则创建OffsetLayer
- 创建RenderObject对应的PaintingContext
- 当RenderObject通过PaintingContext获取canvas进行实质性绘制的时候就会启动record(记录canvas的绘制,并在最后endRecord的时候生成图像)
- 进行RenderObject或者其child的paint操作,由下面的部分源码可知,如果遇到child是RepaintBoundary,会提前stopRecord,并且在_compositeChild方法中将是RepaintBoundary的child的layer直接加到layer树中。
- 将layer加到layer树中。
- 将layer中的picture提交gpu进行渲染
- 由上可知,isRepaintBoundary的RenderObject会有对应的layer,且两个isRepaintBoundary RenderObject之间的RenderObject会被绘制成一个picture。
class PaintingContext
/// Paint a child [RenderObject].
///
/// If the child has its own composited layer, the child will be composited
/// into the layer subtree associated with this painting context. Otherwise,
/// the child will be painted into the current PictureLayer for this context.
void paintChild(RenderObject child, Offset offset) {
...
if (child.isRepaintBoundary) {
stopRecordingIfNeeded();
_compositeChild(child, offset);
} else {
child._paintWithContext(this, offset);
}
...
}
void _compositeChild(RenderObject child, Offset offset) {
assert(!_isRecording);
assert(child.isRepaintBoundary);
assert(_canvas == null || _canvas!.getSaveCount() == 1);
// Create a layer for our child, and paint the child into it.
if (child._needsPaint) {
repaintCompositedChild(child, debugAlsoPaintedParent: true);
} else {
assert(() {
// register the call for RepaintBoundary metrics
child.debugRegisterRepaintBoundaryPaint(
includedParent: true,
includedChild: false,
);
child._layer!.debugCreator = child.debugCreator ?? child;
return true;
}());
}
assert(child._layer is OffsetLayer);
final OffsetLayer childOffsetLayer = child._layer as OffsetLayer;
childOffsetLayer.offset = offset;
appendLayer(child._layer!);
}
class Layer
...
/// This layer's next sibling in the parent layer's child list.
Layer? get nextSibling => _nextSibling;
Layer? _nextSibling;
/// This layer's previous sibling in the parent layer's child list.
Layer? get previousSibling => _previousSibling;
Layer? _previousSibling;
...
layer提交给gpu
flutter_layer.png
由上图可知,layer树提交给gpu流程如下:
- RendererBinding的drawFrame方法中调用RenderView的compositeFrame方法
- RenderView的layer执行buildScene方法
- 遍历layer树,如果layer能复用(
!_needsAddToScene && _engineLayer != null
),则直接将_engineLayer传递给SceneBuilder,否则通过SceneBuilder的pushxxx方法创建EngineLayer - 通过SceneBuilder的build方法创建Scene对象
- 将scene对象传递给Window的render方法。
//class ContainerLayer
@override
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
addChildrenToScene(builder, layerOffset);
}
void addChildrenToScene(ui.SceneBuilder builder, [ Offset childOffset = Offset.zero ]) {
Layer? child = firstChild;
while (child != null) {
if (childOffset == Offset.zero) {
child._addToSceneWithRetainedRendering(builder);
} else {
child.addToScene(builder, childOffset);
}
child = child.nextSibling;
}
}
void _addToSceneWithRetainedRendering(ui.SceneBuilder builder) {
if (!_needsAddToScene && _engineLayer != null) {
builder.addRetained(_engineLayer!);
return;
}
addToScene(builder);
_needsAddToScene = false;
}
//class Layer
void markNeedsAddToScene() {
// Already marked. Short-circuit.
if (_needsAddToScene) {
return;
}
_needsAddToScene = true;
}