flutter 渲染管道是如何优化的

flutter 渲染管道

首先从一段代码开始(flutter/rendering/binding.dart):

void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    if (sendFramesToEngine) {
      renderView.compositeFrame();  
      pipelineOwner.flushSemantics();
      _firstFrameSent = true;
    }
  }

这是 flutter 渲染管道的流程,也就是:

flushLayout
flushCompositingBits
flushPaint

这是我们现在最关心的几个阶段。
单击方法查看对应源码,其实都很相似。

flushLayout

flutter/rendering/Object.dart#PipeLineOwner:

 List<RenderObject> _nodesNeedingLayout = <RenderObject>[];
 	 ... (省略)
	 while (_nodesNeedingLayout.isNotEmpty) {
       final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
       _nodesNeedingLayout = <RenderObject>[];
       for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
          if (node._needsLayout && node.owner == this)
            node._layoutWithoutResize();
        }
      }
     ...

也就是说要重新布局的RenderObject ,是要先添加到这个 List 中才行,而 markNeedsLaout 正是执行此操作的方法,flutter/rendering/Object.dart#RenderObject.markNeedsLaout(简化):

...
 if (_relayoutBoundary != this) {
      markParentNeedsLayout();
    } else {
      _needsLayout = true;
      if (owner != null) {
        owner!._nodesNeedingLayout.add(this);
        owner!.requestVisualUpdate();
      }
    }
...

如果_relayoutBoundary 不是对象本身而是parent,那就执行markParentNeedsLayout,最后调用parent.markNeedsLayout(),这是在确定由哪一方决定布局的;

_relayoutBoundary

_relayoutBoundary 是如何确定的呢,在layout方法中,是由几个条件变量确定的;
flutter/rendering/Object.dart#RenderObject.layout(简化):

...
  RenderObject? relayoutBoundary;
  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;
  if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
    visitChildren(_cleanChildRelayoutBoundary);
  }
  _relayoutBoundary = relayoutBoundary; // 确定了_relayoutBoundary
  if (sizedByParent) {
    try {
      performResize();
    } catch (e, stack) {
      _debugReportException('performResize', e, stack);
    }
  }
  RenderObject? debugPreviousActiveLayout;
  try {
    performLayout();
    markNeedsSemanticsUpdate();
  } catch (e, stack) {
    _debugReportException('performLayout', e, stack);
  }
  _needsLayout = false;
  markNeedsPaint(); // 布局之后是要重新绘制的
...

布局方面的优化几乎都在这里说明了:

  • parentUsesSize

    parent调用child.layout时传递的参数,默认为false,当为true时,parent可以使用child.size,否则会报错;

  • sizedByParent

  • constraints.isTight

// Whether the constraints are the only input to the sizing algorithm (in
// particular, child nodes have no impact).
//
// Returning false is always correct, but returning true can be more
// efficient when computing the size of this render object because we don't
// need to recompute the size if the constraints don't change.
//
// Typically, subclasses will always return the same value. If the value can
// change, then, when it does change, the subclass should make sure to call
// [markNeedsLayoutForSizedByParentChange].
//
// Subclasses that return true must not change the dimensions of this render
// object in [performLayout]. Instead, that work should be done by
// [performResize] or - for subclasses of [RenderBox] - in
// [RenderBox.computeDryLayout].
@protected
bool get sizedByParent => false;

当要重新设计RenderObject时,可以重写sizeByParent
当表达式返回true时,_relayoutBoundary为它本身,并且size(RenderBox)是固定的,除非重新布局,这是因为size是要在performResizecomputeDryLayout设置,并且不应在performLayout中设置size,这是因为重新布局时,只会调用performLayout

flushCompositingBits

示例:

context.pushClipRect(needsCompositing, offset, Offset.zero & size,
	defaultPaint, oldLayer: _clipRectLayer);

调用markNeedsCompositingBitsUpdaterenderObject添加到_nodesNeedingCompositingBitsUpdate,如果needsCompositing发生改变时,将会调用markNeedsPaint;
这个步骤是合成的关键,如果我们深入探索的话,就会发现都是在layer上绘画的。

flushPaint

先看markNeedsPaint的源码,flutter/rendering/Object.dart#RenderObject.markNeedsPaint(简化):

...
if (isRepaintBoundary) {
  if (owner != null) {
    owner!._nodesNeedingPaint.add(this);
    owner!.requestVisualUpdate();
  }
} else if (parent is RenderObject) {
  final RenderObject parent = this.parent! as RenderObject;
  parent.markNeedsPaint();
} else {
  if (owner != null)
    owner!.requestVisualUpdate();
}
...

当调用renderObjectmarkNeedsPaint时,会检查isRepaintBoundary的值,如果为true,parent不用重新绘制,也就是把本身标记为dirtyNodes,从而在渲染管道中被处理。

再从flutter/rendering/Object.dart#PipelineOwner.flushPaint 跟踪代码到

child._paintWithContext(childContext, Offset.zero);

_paintWithContext内部调用在RenderObject中重写的paint方法,这里就要说一下PaintingContext,在绘制child时是要调用context.paintChild的;

flutter/rendering/Object.dart#PaintingContext.paintChild:

...
void paintChild(RenderObject child, Offset offset) {
  if (child.isRepaintBoundary) {
  stopRecordingIfNeeded();
  _compositeChild(child, offset);
  } else {
  child._paintWithContext(this, offset);
  }
  ...
}
...
void _compositeChild(RenderObject child, Offset offset) {
  if (child._needsPaint) {
    repaintCompositedChild(child, debugAlsoPaintedParent: true);
  } else {
  ...
  }
}
...

现在如果我们把它们联系起来,就会发现当parent需要重新绘制时,但是child.isRepaintBoundarytrue,此时只有child._needsPainttrue时,child才会重新绘制,这也就是为什么其他Widget有时要用RepaintBoundary包裹起来的原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值