zoukankan      html  css  js  c++  java
  • 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。

  • 相关阅读:
    NSURLRequest的缓存策略
    React-Native安装使用
    iOS开发--XMPPFramework--环境的配置(一)
    iOS9 HTTP 网络访问问题
    数据交换的三种方法
    iOS项目--古典音乐浏览
    iOS学习笔记33
    iOS bug 日志 -frame 和 bounds的区别
    iOS学习笔记32
    iOS项目 画图小程序
  • 原文地址:https://www.cnblogs.com/muouren/p/14245715.html
Copyright © 2011-2022 走看看