Flutter 学习之旅(三十九) Flutter Render
RenderObject的主要职责就是layout和绘制作用,而整个layout的过程,如果去看RenderObject的源码其实还是比较混乱的,说实话我看了很久,但是每次想写的时候感觉都比较混乱,没有明确的思路,不是说哪一个方法没有看懂,而是没有一条主线将所有的方法串联起来,直到我从网上一篇文章中看到了一个说法给了我思路,如果想要看layout的过程,可以从RendererBinding 的drawFrame开始,那里是每一帧重绘的起点,从这里开始看,让我将整个过程连接起来,恍然大悟.
整个layout的过程大致分为2步,
1> RenderObject.attach(PipelineOwner owner),表示开始依赖布局,标记需要layout的RenderObject 为dirty ,将它放入需要layout的list中,等待下一帧layout,这个是首次layout的过程,如果只是更新的话,则没有attach的这个过程
调用RenderObject 的 markNeedsLayout方法向上查找,找到布局边界的parentView,并将parentView 的 RenderObject添加到owner._nodesNeedingLayout的list中, 接下来系统会在parentView.flushLayout的方法中以出栈的形式开始layout,
2>layout 过程
RendererBinding.drawFrame() > parentView.flushLayout() > parentView._layoutWithoutResize() > parentView.performLayout() > child.layout() >(child.performResize() 这个可能不执行) child. performLayout() > subchild.layout() ......
这里的parentView 是布局边界中的父布局, subchild 是child 的子布局,使用的是这种循环嵌套的过程,直到没有子view
下面我会贴出代码,但是会去掉一些assert 方法和 抛异常的方法,否则无用代码太多影响阅读和注释,
这里先从第一步来, attach 和 markNeedsLayout
@override
void attach(PipelineOwner owner) {
super.attach(owner);
///如果是dirty并且为布局边界 则开始layout
if (_needsLayout && _relayoutBoundary != null) {
_needsLayout = false;
markNeedsLayout();///开始布局
}
if (_needsCompositingBitsUpdate) {
_needsCompositingBitsUpdate = false;
markNeedsCompositingBitsUpdate();
}
if (_needsPaint && _layer != null) {
_needsPaint = false;
markNeedsPaint();
}
if (_needsSemanticsUpdate && _semanticsConfiguration.isSemanticBoundary) {
_needsSemanticsUpdate = false;
markNeedsSemanticsUpdate();
}
}
///首次加载 刷新都会调用该方法,此方法可以让RenderObject 刷新
void markNeedsLayout() {
///如果已经是dirty 的状态,则不需要重新标记
if (_needsLayout) {
return;
}
if (_relayoutBoundary != this) {
markParentNeedsLayout(); ///不是布局边界,向上查找,
} else {
////是布局边界,将此标记为dirty,并且添加到等待刷新布局中,等待layout
_needsLayout = true;
if (owner != null) {
owner._nodesNeedingLayout.add(this);
owner.requestVisualUpdate();
}
}
}
第二步 layout 过程
layou的过程中由于不同的布局有不同的表现,所以我们这里以Padding() 为例来说明
layout开始的地方是从RendererBinding.drawFrame() 开始的
void drawFrame() {
pipelineOwner.flushLayout();/// 开始布局
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();///重绘
if (sendFramesToEngine) {
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
_firstFrameSent = true;
}
}
从代码里面可以看到从哪里开始布局,这里我们只关心layout的过程,其他的暂时不关心
接下来再看flushLayout()方法
void flushLayout() {
if (!kReleaseMode) {
Timeline.startSync('Layout', arguments: timelineArgumentsIndicatingLandmarkEvent);
}
try {
///遍历 markNeedsLayout 方法中添加的RenderObject
while (_nodesNeedingLayout.isNotEmpty) {
final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
///depth 是深度,如果深度越浅越在前面
for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
///是dirty状态,并且 owner是自身
if (node._needsLayout && node.owner == this)
node._layoutWithoutResize();
}
}
} finally {
if (!kReleaseMode) {
Timeline.finishSync();
}
}
}
void _layoutWithoutResize() {
try {
performLayout();///执行parentView 的performLayout ,
markNeedsSemanticsUpdate();///更新语义
} catch (e, stack) {
_debugReportException('performLayout', e, stack);
}
_needsLayout = false;///layout 后 将dirty 状态标记为false
markNeedsPaint();///重绘
}
上面那两个方法,只是说明一下parentView 的 开始layout的过程,performLayout 是由RenderObject的子类来实现的,原因是不同的布局有不同的表现,这里我拿Padding来说明一下
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
_resolve();
assert(_resolvedPadding != null);
if (child == null) {
size = constraints.constrain(Size(
_resolvedPadding.left + _resolvedPadding.right,
_resolvedPadding.top + _resolvedPadding.bottom,
));
return;
}
final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding);
/// 在 parentView 的performLayout中执行child 的layout 方法,
child.layout(innerConstraints, parentUsesSize: true);
final BoxParentData childParentData = child.parentData as BoxParentData;
childParentData.offset = Offset(_resolvedPadding.left, _resolvedPadding.top);
size = constraints.constrain(Size(
_resolvedPadding.left + child.size.width + _resolvedPadding.right,
_resolvedPadding.top + child.size.height + _resolvedPadding.bottom,
));
}
这里有很多计算布局的方法,这里不需要理会,我们会在下一篇来分析,我们关心的是layout的过程,在parentView中执行child的layout方法,
void layout(Constraints constraints, { bool parentUsesSize = false }) {
RenderObject relayoutBoundary;
///节点布局变化不影响父节点 ,或者size由父节点决定 或者父节点不是 RenderObject
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;
///如果前后的布局边界不相同,则清除原来child的布局边界
if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
visitChildren(_cleanChildRelayoutBoundary);
}
_relayoutBoundary = relayoutBoundary;
///child的边界由parent来决定,则执行performResize
if (sizedByParent) {
try {
performResize();///在子方法中实现,在Padding 中是将parentView 的size 赋值给自身
} catch (e, stack) {
_debugReportException('performResize', e, stack);
}
}
try {
performLayout();///执行子View 的 performLayout ,如果子View 还是padding的话,则继续执行此循环
markNeedsSemanticsUpdate();
} catch (e, stack) {
_debugReportException('performLayout', e, stack);
}
_needsLayout = false;
markNeedsPaint();
}
parentUsesSize 表示子节点布局变化是否影响父节点
sizedByParent 表示 child size 完成由 parentView 来决定,所以当 sizedByParent 为 true 时,child size 在 performResize 中确定。当 sizedByParent 为 false 时,执行 performLayout 计算自身 size,
relayoutBoundary 布局边界,他的意思就是如果一个child 的大小如果影响到了父节点大小,在markNeedsLayout 方法中就会向上查找parentView ,直到 relayoutBoundary 为 true ,即布局边界,
在layout 方法中又执行了子View 的performLayout 的方法,如此循环,就形成了整个layout布局的过程,
希望整个layout过程会对你的学习过程有一些帮助,整理后的过程还是非常清晰的,
后续的计算布局的大小和绘制过程,我们在下一篇来分析,
我学习flutter的整个过程都记录在里面了
https://www.jianshu.com/c/36554cb4c804
最后附上demo 地址