Flutter中RenderObject布局
RenderObject 是主要负责实现视图渲染的对象。每个 Element都对应一个RenderObject。Flutter 通过控件树(Widget 树)中的每个控件(Widget)创建不同类型的渲染对象,组成渲染对象树。而渲染对象树在 Flutter 的展示过程分为四个阶段,即布局、绘制、合成和渲染。 其中,布局和绘制在 RenderObject 中完成。abstract cla...
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() { }
}
更多推荐



所有评论(0)