flutter_5_深入_2_深入layout、paint流程

参考

http://gityuan.com/flutter/

从架构到源码:一文了解Flutter渲染机制

Flutter渲染原理学习

Flutter完整开发实战详解(九、 深入绘制原理)

Flutter完整开发实战详解(二十一、 Flutter 画面渲染的全面解析)

layout

请求layout

首次

RendererBinding

@override
void initInstances() {
。。。
  initRenderView();
。。。
}

void initRenderView() {
  renderView = RenderView(configuration: createViewConfiguration(), window: window);
  renderView.prepareInitialFrame();
}

 

RenderView

void prepareInitialFrame() {
  scheduleInitialLayout();
  scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
}

void scheduleInitialLayout() {
  _relayoutBoundary = this;
  owner!._nodesNeedingLayout.add(this);
}

owner是PipelineOwner。

 

上边流程可知,初始化时会把root加入到需要布局list中,那么在第一帧到来时就会调用其layout进行布局。

 

markNeedsLayout

上边是初始时的必须的layout,如果在ui运行起来后布局参数发生了变化需要重新布局就需要调用此方法:

void markNeedsLayout() {
  if (_needsLayout) {
    return;
  }

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

这里涉及到了relayoutBoundary的概念,flutter会把从上到下相邻的几个renderObject按照规则当成一组来处理layout,规则是如果子renderObject的layout会影响父renderObject的大小、位置,那么就会把这些相互影响的renderObject当成一组。这样做的目的很明显,就是为了减少不必要的layout。

 

markNeedsLayout()中如果relayoutBoundary和当前的renderObject不相等,那说明它和它的parent是一组,那么就会去请求parent layout:

void markParentNeedsLayout() {
  _needsLayout = true;

  finaRenderObject parent = this.parent! as RenderObject;
// 如果当前没有处于layout阶段,那么就 请求layout
  if (!_doingThisLayoutWithCallback) {
    parent.markNeedsLayout();
  }
}

 

如果relayoutBoundary和当前的renderObject相等,就会把当前的renderObject加入到一个需要layout的list中,并请求一下PipelineOwner.requestVisualUpdate:

void requestVisualUpdate() {
  if (onNeedVisualUpdate != null)
    onNeedVisualUpdate!();
}

onNeedVisualUpdate是一个VoidCallback,它是在RendererBinding.initInstances中加入的

void initInstances() {
  super.initInstances();
  _instance = this;
  _pipelineOwner = PipelineOwner(
    onNeedVisualUpdate: ensureVisualUpdate,
    onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
    onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
  );
}

void ensureVisualUpdate() {
  switch (schedulerPhase) {
    case SchedulerPhase.idle:
    case SchedulerPhase.postFrameCallbacks:
      scheduleFrame();
      return;
    case SchedulerPhase.transientCallbacks:
    case SchedulerPhase.midFrameMicrotasks:
    case SchedulerPhase.persistentCallbacks:
      return;
  }
}

void scheduleFrame() {
  if (_hasScheduledFrame || !framesEnabled)
    return;
  ensureFrameCallbacksRegistered();
  window.scheduleFrame();
  _hasScheduledFrame = true;
}

最后会去调用底层window.scheduleFrame()来注册一个下一帧时回调,就类似于Android中的ViewRootImpl.scheduleTraversals()。

 

触发layout

下一帧来到后,会调用到的方法是在上边的ensureFrameCallbacksRegistered()中注册的回调,

SchedulerBinding.ensureFrameCallbacksRegistered

void ensureFrameCallbacksRegistered() {
  window.onBeginFrame ??= _handleBeginFrame;
  window.onDrawFrame ??= _handleDrawFrame;
}

onBeginFrame :主要是用来执行动画,

onDrawFrame :这个主要处理上边说的persistentCallbacks

 

void handleDrawFrame() {
  try {
    // PERSISTENT FRAME CALLBACKS
    _schedulerPhase = SchedulerPhase.persistentCallbacks;
    for (finaFrameCallback callback in _persistentCallbacks)
      _invokeFrameCallback(callback, _currentFrameTimeStamp!);

    // POST-FRAME CALLBACKS
    _schedulerPhase = SchedulerPhase.postFrameCallbacks;
    finaList<FrameCallback> localPostFrameCallbacks =
        List<FrameCallback>.from(_postFrameCallbacks);
    _postFrameCallbacks.clear();
    for (finaFrameCallback callback in localPostFrameCallbacks)
      _invokeFrameCallback(callback, _currentFrameTimeStamp!);
  } finally {
  }
}

看下persistentCallbacks列表在哪添加的callback,最终找到是在RendererBinding.initInstances中添加的callback,

void initInstances() {
  addPersistentFrameCallback(_handlePersistentFrameCallback);
}

 

接着上边handleDrawFrame的流程,RendererBinding中:

void _handlePersistentFrameCallback(Duration timeStamp) {
  drawFrame();
}

void drawFrame() {
  assert(renderView != null);
  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;
  }
}

 

pipelineOwner.flushLayout()

void flushLayout() {
  try {
    while (_nodesNeedingLayout.isNotEmpty) {
      finaList<RenderObject> dirtyNodes = _nodesNeedingLayout;
      _nodesNeedingLayout = <RenderObject>[];
      for (finaRenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
        if (node._needsLayout && node.owner == this)
          node._layoutWithoutResize();
      }
    }
  } finally {
  }
}

会按照从上到下的顺序进行layout。

 

RenderObject._layoutWithoutResize()

void _layoutWithoutResize() {
  try {
    performLayout();
  } catch (e, stack) {
  }
  _needsLayout = false;

  markNeedsPaint();
}

可以看到会先执行performLayout,然后又调用了markNeedsPaint(),因为有可能改变了大小位置会影响绘制的内容。

 

layout流程

一般父renderObject在performLayout时是会调用child的layout的

void layout(Constraints constraints, { booparentUsesSize = false }) {
  if (!kReleaseMode && debugProfileLayoutsEnabled)
    Timeline.startSync('$runtimeType',  arguments: timelineArgumentsIndicatingLandmarkEvent);

  RenderObject? relayoutBoundary;
  if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
    relayoutBoundary = this;
  } else {
    relayoutBoundary = (parent as RenderObject)._relayoutBoundary;
  }

// 如果当前就是relayoutBoundary,并且没被请求layout,constraints 也和之前一样,那么直接return。
  if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {
    if (!kReleaseMode && debugProfileLayoutsEnabled)
      Timeline.finishSync();
    return;
  }

  _constraints = constraints;
  if (_relayoutBoundary != nul&& relayoutBoundary != _relayoutBoundary) {
    // The locarelayout boundary has changed, must notify children in case
    // they also need updating. Otherwise, they wilbe confused about what
    // their actuarelayout boundary is later.
    visitChildren(_cleanChildRelayoutBoundary);
  }
  _relayoutBoundary = relayoutBoundary;


  if (sizedByParent) {
    try {
      performResize();
    } catch (e, stack) {
      _debugReportException('performResize', e, stack);
    }
  }

  try {
    performLayout();
    markNeedsSemanticsUpdate();
  } catch (e, stack) {
    _debugReportException('performLayout', e, stack);
  }

  _needsLayout = false;
  markNeedsPaint();

  if (!kReleaseMode && debugProfileLayoutsEnabled)
    Timeline.finishSync();
}

1. 确定当前RenderObject对应的relayoutBoundary,就是需要重新布局的边界或范围。

2. 调用performResize或performLayout去确定自己的size

 

  • 参数constraints代表了parent传入的约束,最后计算得到的RenderObject的size必须符合这个约束。
  • 参数parentUsesSize代表parent是否会使用child的size,它参与计算repaintBoundary,可以对Layout过程起到优化作用。
  • sizedByParent是RenderObject的一个属性,默认为false,子类可以去重写这个属性。顾名思义,sizedByParent表示RenderObject的size的计算完全由其parent决定。换句话说,也就是RenderObject的size只和parent给的constraints有关,与自己children的sizes无关。同时,sizedByParent也决定了RenderObject的size需要在哪个方法中确定,若sizedByParent为true,那么size必须得在performResize方法中确定,否则size需要在performLayout中确定。
  • performResize()方法的作用是确定size,实现该方法时需要根据parent传入的constraints确定RenderObject的size。
  • performLayout()则除了用于确定size以外,还需要负责遍历调用child.layout方法对计算children的sizes和offsets。

 

和Android的layout的不同

  • Android上请求layout时,会把请求layout的view 及其 祖先view都标记一下表示需要layout,那么下一帧时就会从顶层view去遍历所有的view,如果遇到标记需要layout的view就继续layout,如果遇到没有标记layout的view那么就会跳过这个view及其子树。
  • 而flutter上请求layout时,只会把请求layout的那一组renderObject标记上需要layout,并把relayoutBoundary的renderObject加入到需要layout的list中,那么在下一帧时就会从list中取出需要layout的relayoutBoundary的renderObject来layout。

总的来看,好像flutter的layout会更有效率一些,因为加入了一个分层的概念。

 

paint

只有在layout之后才能进行绘制。如果发现要绘制的renderObject的needLayout为true,那么就直接返回不绘制了,因为此时绘制无意义,重新布局后也会进行重绘。

layer

绘制涉及到一个Layer和RepaintBoundary的概念,其实和Android硬件加速的DisplayList是类似的东西。

一个layer中的所有renderObject都会把其绘制命令保存在一个layer上,一个layer中只要有一个renderObject调用markNeedPaint(不管其在哪层),那么同一个layer的所有的renderObject都会被重绘,如果其中有一个renderObject的一个子renderObject是RepaintBoundary(也就是一个新的layer),那么可以直接复用其缓存。

其实就是减少不必要的重绘。

flutter就提供了一个现成的widget——RepaintBoundary,来让我们方便的把一个组件变为一个新的layer。

Layer的类型

flutter中有很多种Layer,主要分为ContainerLayer和 叶子Layer。

 

ContainerLayer 是可以具备子节点,也就是带有 append 方法,大致可以分为:

  • 位移类(OffsetLayer/TransformLayer);
  • 透明类(OpacityLayer)
  • 裁剪类(ClipRectLayer/ClipRRectLayer/ClipPathLayer);
  • 阴影类 (PhysicalModelLayer)
  • ShaderMaskLayer:着色层,可以指定着色器矩阵和混合模式参数。
  • ColorFilterLayer:颜色过滤层,可以指定颜色和混合模式参数。
  • BackdropFilterLayer:背景过滤层,可以指定背景图参数。

为什么这些 Layer 需要是 ContainerLayer ?因为这些 Layer 都是一些像素合成的操作,其本身是不具备“描绘”控件的能力,就如前面的蓝色小方块例子一样,如果要呈现画面一般需要和 PictureLayer 结合。

比如 RenderClipRRect内部,在 pushClipRRect 时可以会创建 ClipRRectLayer ,而新创建的 ClipRRectLayer 会通过 appendLayer 方法触发 append 操作添加为父 Layer 的子节点。

 

叶子Layer一般不具备子节点,比如:

  • PictureLayer 是用于绘制画面,Flutter 上的控件基本是绘制在这上面;
  • TextureLayer 是用于外界纹理,比如视频播放或者摄像头数据;
  • PlatformViewLayer 是用于 iOS 上 PlatformView 相关嵌入纹理的使用;

请求paint

首次

RendererBinding

@override
void initInstances() {
。。。
  initRenderView();
。。。
}

void initRenderView() {
  renderView = RenderView(configuration: createViewConfiguration(), window: window);
  renderView.prepareInitialFrame();
}

 

RenderView

void prepareInitialFrame() {
  scheduleInitialLayout();
  scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
}

void scheduleInitialPaint(ContainerLayer rootLayer) {
  _layer = rootLayer;
  owner!._nodesNeedingPaint.add(this);
}

 

上边流程可知,初始化时会把root加入到需要重绘list中,那么在第一帧到来时就会重绘。

 

markNeedsPaint

对于非第一次,需要调用RenderObject.markNeedsPaint。

void markNeedsPaint() {
  if (_needsPaint)
    return;
  _needsPaint = true;

  if (isRepaintBoundary) {
    // If we always have our own layer, then we can just repaint
    // ourselves without involving any other nodes.
    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 we're the root of the render tree (probably a RenderView),
    // then we have to paint ourselves, since nobody else can paint
    // us. We don't add ourselves to _nodesNeedingPaint in this
    // case, because the root is always told to paint regardless.
    if (owner != null)
      owner!.requestVisualUpdate();
  }
}

此方法分为三个逻辑:

1. 如果是RepaintBoundary,那么就把它加入到_nodesNeedingPaint,并请求下一帧回调。

2. 如果不是RepaintBoundary,那么就往上找,

3. 始终想不通为什么会调用这里,因为RenderView.isRepaintBoundary为true。

 

PipelineOwner.requestVisualUpdate在上边的layout也有,所以直接看下一帧来时的重绘流程。

 

触发paint

在上边的触发layout中的drawFrame中有一步就是进行重绘的:

void drawFrame() {
  assert(renderView != null);
  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;
  }
}

 

pipelineOwner.flushPaint()

void flushPaint() {
  try {
    final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
    _nodesNeedingPaint = <RenderObject>[];
    // Sort the dirty nodes in reverse order (deepest first).
    for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) 
      if (node._needsPaint && node.owner == this) {
        if (node._layer!.attached) {
          PaintingContext.repaintCompositedChild(node);
        } else {
          node._skippedPaintingOnLayer();
        }
      }
    }
  } finally {
  }
}

 

PaintingContext.repaintCompositedChild

static void repaintCompositedChild(RenderObject child, { bool debugAlsoPaintedParent = false }) {
  assert(child._needsPaint);
  _repaintCompositedChild(
    child,
    debugAlsoPaintedParent: debugAlsoPaintedParent,
  );
}

static void _repaintCompositedChild(
  RenderObject child, {
  bool debugAlsoPaintedParent = false,
  PaintingContext? childContext,
}) {
  OffsetLayer? childLayer = child._layer as OffsetLayer?;
  if (childLayer == null) {
    // Not using the `layer` setter because the setter asserts that we not
    // replace the layer for repaint boundaries. That assertion does not
    // apply here because this is exactly the place designed to create a
    // layer for repaint boundaries.
    child._layer = childLayer = OffsetLayer();
  } else {
    childLayer.removeAllChildren();
  }
  childContext ??= PaintingContext(child._layer!, child.paintBounds);
  child._paintWithContext(childContext, Offset.zero);

  childContext.stopRecordingIfNeeded();
}

由于加入到_nodesNeedingPaint中的RenderObject都是RepaintBoundary,也就是一个layer所有者,所以就先看是否需要创建一个OffsetLayer,

如果不需要就把之前的绘制子layer都给清空了,便于之后重绘。

接着就是重建一个PaintingContext对象,并调用renderObject的_paintWithContext来进行往下重绘。

 

RenderObject._paintWithContext

void _paintWithContext(PaintingContext context, Offset offset) {
  if (_needsLayout)
    return;
  _needsPaint = false;
  try {
    paint(context, offset);
  } catch (e, stack) {
  }
}

 

你可能会有疑问,就是如果RepaintBoundary的RenderObject也需要绘制自身,而它的layer是一个ContainerLayer并不能绘制,怎么办呢?

其实PaintingContext中首次访问canvas时会进行一些初始化:

Canvas get canvas {
  if (_canvas == null)
    _startRecording();
  return _canvas!;
}

void _startRecording() {
  assert(!_isRecording);
  _currentLayer = PictureLayer(estimatedBounds);
  _recorder = ui.PictureRecorder();
  _canvas = Canvas(_recorder!);
  _containerLayer.append(_currentLayer!);
}

可以看到这里边创建了一个子layer——PictureLayer用于该layer中需要绘制内容的一个存放点。

 

paint

一般父renderObject会调用PaintingContext.paintChild来绘制child。

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

这个方法分两个逻辑:

1. 如果child是RepaintBoundary,也就是另一个layer,那么就需要停止当前layer的绘制,进行子layer的绘制

2. 如果不是RepaintBoundary,那么就继续调用child.paint,把child绘制在当前layer上。

 

停止当前layer的绘制,PaintingContext

void stopRecordingIfNeeded() {
  if (!_isRecording)
    return;
  _currentLayer!.picture = _recorder!.endRecording();
  _currentLayer = null;
  _recorder = null;
  _canvas = null;
}

 

进行子layer的绘制,PaintingContext

void _compositeChild(RenderObject child, Offset offset) {
  // Create a layer for our child, and paint the child into it.
  if (child._needsPaint) {
    repaintCompositedChild(child, debugAlsoPaintedParent: true);
  } else {
  }
  assert(child._layer is OffsetLayer);
  final OffsetLayer childOffsetLayer = child._layer as OffsetLayer;
  childOffsetLayer.offset = offset;
  appendLayer(child._layer!);
}

void appendLayer(Layer layer) {
  layer.remove();
  _containerLayer.append(layer);
}

这里边也分两步,

1. 首先是判读是否child需要重绘,如果需要才进行repaintCompositedChild(在触发paint介绍过了),也就是绘制子layer,

2. 然后就是把子layer添加到父layer中,如果没有重绘,子layer就是之前绘制的一个缓存,从而构成了一个layer tree。

posted @ 2021-01-07 12:01  嘤嘤嘤123  阅读(540)  评论(1编辑  收藏  举报