美文网首页Flutter记录自学flutter点点滴滴
Flutter 学习之旅(三十九) Flutter Render

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

作者: Tsm_2020 | 来源:发表于2020-09-29 16:08 被阅读0次

    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

    相关文章

      网友评论

        本文标题:Flutter 学习之旅(三十九) Flutter Render

        本文链接:https://www.haomeiwen.com/subject/krqluktx.html