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。

  • 相关阅读:
    数据类型装换
    变量及数据类型
    27 网络通信协议 udp tcp
    26 socket简单操作
    26 socket简单操作
    14 内置函数 递归 二分法查找
    15 装饰器 开闭原则 代参装饰器 多个装饰器同一函数应用
    12 生成器和生成器函数以及各种推导式
    13 内置函数 匿名函数 eval,exec,compile
    10 函数进阶 动态传参 作用域和名称空间 函数的嵌套 全局变量
  • 原文地址:https://www.cnblogs.com/muouren/p/14245715.html
Copyright © 2011-2022 走看看