Flutter中RenderObject布局

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() { }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值