flutter 渲染管道
首先从一段代码开始(flutter/rendering/binding.dart):
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
if (sendFramesToEngine) {
renderView.compositeFrame();
pipelineOwner.flushSemantics();
_firstFrameSent = true;
}
}
这是 flutter 渲染管道的流程,也就是:
graph LR
A[flushLayout] --> B(flushCompositingBits) --> C[flushPaint]
这是我们现在最关心的几个阶段。 单击方法查看对应源码,其实都很相似。
flushLayout
flutter/rendering/Object.dart#PipeLineOwner:
List<RenderObject> _nodesNeedingLayout = <RenderObject>[];
... (省略)
while (_nodesNeedingLayout.isNotEmpty) {
final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
if (node._needsLayout && node.owner == this)
node._layoutWithoutResize();
}
}
...
也就是说要重新布局的RenderObject
,是要先添加到这个 List
中才行,而 markNeedsLaout
正是执行此操作的方法,flutter/rendering/Object.dart#RenderObject.markNeedsLaout(简化):
...
if (_relayoutBoundary != this) {
markParentNeedsLayout();
} else {
_needsLayout = true;
if (owner != null) {
owner!._nodesNeedingLayout.add(this);
owner!.requestVisualUpdate();
}
}
...
如果_relayoutBoundary
不是对象本身而是parent
,那就执行markParentNeedsLayout
,最后调用parent.markNeedsLayout()
,这是在确定由哪一方决定布局的;
_relayoutBoundary
_relayoutBoundary
是如何确定的呢,在layout
方法中,是由几个条件变量确定的;
flutter/rendering/Object.dart#RenderObject.layout(简化):
...
RenderObject? relayoutBoundary;
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
relayoutBoundary = this;
} else {
relayoutBoundary = (parent! as RenderObject)._relayoutBoundary;
}
if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {
return;
}
_constraints = constraints;
if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
visitChildren(_cleanChildRelayoutBoundary);
}
_relayoutBoundary = relayoutBoundary; // 确定了_relayoutBoundary
if (sizedByParent) {
try {
performResize();
} catch (e, stack) {
_debugReportException('performResize', e, stack);
}
}
RenderObject? debugPreviousActiveLayout;
try {
performLayout();
markNeedsSemanticsUpdate();
} catch (e, stack) {
_debugReportException('performLayout', e, stack);
}
_needsLayout = false;
markNeedsPaint(); // 布局之后是要重新绘制的
...
布局方面的优化几乎都在这里说明了:
-
parentUsesSize
-
sizedByParent
-
constraints.isTight
// Whether the constraints are the only input to the sizing algorithm (in
// particular, child nodes have no impact).
//
// Returning false is always correct, but returning true can be more
// efficient when computing the size of this render object because we don't
// need to recompute the size if the constraints don't change.
//
// Typically, subclasses will always return the same value. If the value can
// change, then, when it does change, the subclass should make sure to call
// [markNeedsLayoutForSizedByParentChange].
//
// Subclasses that return true must not change the dimensions of this render
// object in [performLayout]. Instead, that work should be done by
// [performResize] or - for subclasses of [RenderBox] - in
// [RenderBox.computeDryLayout].
@protected
bool get sizedByParent => false;
当要重新设计RenderObject
时,可以重写sizeByParent
。
当表达式返回true
时,_relayoutBoundary
为它本身,并且size
(RenderBox)是固定的,除非重新布局,这是因为size
是要在performResize
或computeDryLayout
设置,并且不应在performLayout
中设置size
,这是因为重新布局时,只会调用performLayout
。
flushCompositingBits
示例:
context.pushClipRect(needsCompositing, offset, Offset.zero & size,
defaultPaint, oldLayer: _clipRectLayer);
调用markNeedsCompositingBitsUpdate
将renderObject
添加到_nodesNeedingCompositingBitsUpdate
,如果needsCompositing
发生改变时,将会调用markNeedsPaint
;
这个步骤是合成的关键,如果我们深入探索的话,就会发现都是在layer
上绘画的。
flushPaint
先看markNeedsPaint
的源码,flutter/rendering/Object.dart#RenderObject.markNeedsPaint(简化):
...
if (isRepaintBoundary) {
if (owner != null) {
owner!._nodesNeedingPaint.add(this);
owner!.requestVisualUpdate();
}
} else if (parent is RenderObject) {
final RenderObject parent = this.parent! as RenderObject;
parent.markNeedsPaint();
} else {
if (owner != null)
owner!.requestVisualUpdate();
}
...
当调用renderObject
的markNeedsPaint
时,会检查isRepaintBoundary
的值,如果为true,parent
不用重新绘制,也就是把本身标记为dirtyNodes
,从而在渲染管道中被处理。
再从flutter/rendering/Object.dart#PipelineOwner.flushPaint 跟踪代码到
child._paintWithContext(childContext, Offset.zero);
_paintWithContext内部调用在RenderObject
中重写的paint
方法,这里就要说一下PaintingContext
,在绘制child
时是要调用context.paintChild
的;
flutter/rendering/Object.dart#PaintingContext.paintChild:
...
void paintChild(RenderObject child, Offset offset) {
if (child.isRepaintBoundary) {
stopRecordingIfNeeded();
_compositeChild(child, offset);
} else {
child._paintWithContext(this, offset);
}
...
}
...
void _compositeChild(RenderObject child, Offset offset) {
if (child._needsPaint) {
repaintCompositedChild(child, debugAlsoPaintedParent: true);
} else {
...
}
}
...
现在如果我们把它们联系起来,就会发现当parent
需要重新绘制时,但是child.isRepaintBoundary
为true
,此时只有child._needsPaint
为true
时,child才会重新绘制,这也就是为什么其他Widget有时要用RepaintBoundary
包裹起来的原因。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!