Flutter记录自学flutter点点滴滴

Flutter 学习之旅(三十九) Flutter Render

2020-09-29  本文已影响0人  Tsm_2020

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 地址

https://github.com/tsm19911014/tsm_flutter

上一篇下一篇

猜你喜欢

热点阅读