RenderObject 是主要负责实现视图渲染的对象。每个 Element都对应一个 RenderObject。
Flutter 通过控件树(Widget 树)中的每个控件(Widget)创建不同类型的渲染对象,组成渲染对象树。而渲染对象树在 Flutter 的展示过程分为四个阶段,即布局、绘制、合成和渲染。 其中,布局和绘制在 RenderObject 中完成。
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
ParentData parentData;
}
class AbstractNode {
int get depth => _depth;
int _depth = 0;
AbstractNode get parent => _parent;
AbstractNode _parent;
Object get owner => _owner;
Object _owner;
}
class ParentData {
@protected
@mustCallSuper
void detach() { }
}
RenderObject 就是渲染树种的一个对象,其拥有一个 parent 和一个 parentData插槽。所谓插槽,就是指预留的一个接口和位置,这个接口或位置是由其他对象来接入或占据,它正是由 parent 赋值的,parent 通常会通过子 RenderObjcet 的 parentData 存储一些子元素相关的数据。
在布局阶段,父节点会调用子节点的 layout 方法
/*
constraints 父节点对子节点大小的限制,该值将根据父节点的布局逻辑来确定
parentUsesSize 为true,那么子节点布局发生变化时,父节点就会标记为需要重新布局,false相反。
*/
void layout(Constraints constraints, { bool parentUsesSize = false }) {
//...
RenderObject relayoutBoundary;
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
relayoutBoundary = this;
} else {
relayoutBoundary = (parent as RenderObject)._relayoutBoundary;
}
assert(() {
_debugCanParentUseSize = parentUsesSize;
return true;
}());
//...
if (sizedByParent) {
//...
try {
performResize();
//...
} catch (e, stack) {
_debugReportException('performResize', e, stack);
}
//...
}
//...
markNeedsPaint();
}
parentUsesSize 表示子节点布局变化是否影响父节点,如果为 true,那么子节点布局发生变化时,父节点就会标记为需要重新布局,如果为false,则子节点布局发生变化后不影响父节点。
sizedByParent 表示该节点的大小是否通过 parent 传递给它的 constraints 就可以确定,即该节点的大小与自身的属性及其子节点无关,比如,如果一个控件永远充满 parent 的大小,那么 sizedByParent 就为 true,其大小由 performResize 确定。
必须了解下 _relayoutBoundary 变量
当一个 Element 标记为 dirty 时(setState 中调用 Elenemt#markNeedsBuild 中标记),这时会重新布局。而在 RenderObject 中调用 markNeedsLayout 标记为 dirty
RenderObject _relayoutBoundary;
List<RenderObject> _nodesNeedingLayout = <RenderObject>[];
@override
PipelineOwner get owner => super.owner as PipelineOwner;
void markNeedsLayout() {
//...
assert(_relayoutBoundary != null);
//判断自身是不是 _relayoutBoundary,如果不是则继续向 parent 查找,一直查到到是 _relayoutBoundary 为止
if (_relayoutBoundary != this) {
markParentNeedsLayout();
} else {
_needsLayout = true;
if (owner != null) {
//...
//标记 为 dirty
owner._nodesNeedingLayout.add(this);
owner.requestVisualUpdate();
}
}
}
void markParentNeedsLayout() {
_needsLayout = true;
final RenderObject parent = this.parent as RenderObject;
if (!_doingThisLayoutWithCallback) {
parent.markNeedsLayout();
} else {
assert(parent._debugDoingThisLayout);
}
assert(parent == this.parent);
}
当一个控件的大小发生变化时可能会影响到它的 parent,因此 parent也需要重新布局。当一个 RenderObject 时 _relayoutBoundary 时,布局结束,表示它的大小不会再影响到 parent 的大小。
BoxConstraints
class BoxConstraints extends Constraints {
final double minWidth;
final double maxWidth;
final double minHeight;
final double maxHeight;
}
Flutter 提供了 RenderBox 类,它继承 RenderObject ,布局坐标系采用笛卡尔坐标系,屏幕的 top、left是原点,然后分宽高两个轴,大多数情况下使用 RenderBox 就可以了。实际的测量和布局在 RenderBox 的 performResize 和 performLayout 方法中。
RenderBox 提供看一个 size 属性用来保存宽高。RenderBox 的 Layout 时通过在组件树上从上往下传递 BoxConstraints 对象来实现的。
@override
void performResize() {
// constraints 中 Size get smallest => Size(constrainWidth(0.0), constrainHeight(0.0));
size = constraints.smallest;
assert(size.isFinite);
}
@override
void performLayout() {
assert(() {
if (!sizedByParent) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('$runtimeType did not implement performLayout().'),
ErrorHint(
'RenderBox subclasses need to either override performLayout() to '
'set a size and lay out any children, or, set sizedByParent to true '
'so that performResize() sizes the render object.'
),
]);
}
return true;
}());
}
所以布局调用栈为 layout > performResize / performLayout > child.layout
Layout 结束后,每个节点的位置已经确定。子节点在父节点中偏移数据通过 parentData 保存。
RenderObject 中
void setupParentData(covariant RenderObject child) {
assert(_debugCanPerformMutations);
if (child.parentData is! ParentData)
child.parentData = ParentData();
}
RenderBox 中
@override
void setupParentData(covariant RenderObject child) {
if (child.parentData is! BoxParentData)
child.parentData = BoxParentData();
}
parentData 结构
class BoxParentData extends ParentData {
// offset 表示子节点在父节点坐标系中的绘制偏移
Offset offset = Offset.zero;
}
class ParentData {
@protected
@mustCallSuper
void detach() { }
}