阅读 82

七、Paint 绘制(2)

一、Flutter 之图像绘制原理

二、Widget、Element、RenderObject

三、Flutter UI 更新流程

四、build 流程分析

五、layout 流程分析

六、Paint 绘制(1)

八、composite 流程分析

九、Flutter 小实践

第六章节简单介绍了 图层,并分析了影响图层创建的元素之一 needsCompositing, 这一章节会重点介绍整个绘制流程。

(1) flushPaint

void flushPaint() {
    final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
    _nodesNeedingPaint = <RenderObject>[];
    for (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();
        }
      }
    }
}
复制代码

上述代码主要是两个步骤 给节点排序 遍历 _nodesNeedingPaint 判断 layer.attached, 如果为为true, 则调用 repaintCompositedChild(node) 去做绘制,否则调用node._skippedPaintingOnLayer

(2) markNeedsPaint

void markNeedsPaint() {
  assert(owner == null || !owner.debugDoingPaint);
  if (_needsPaint)
    return;
  _needsPaint = true;
  if (isRepaintBoundary) {
    //如果当前节点 isRepaintBoundary 为 true, 则将该节点添加到      _nodesNeedingPaint结合中
    if (owner != null) {
      owner._nodesNeedingPaint.add(this);
      owner.requestVisualUpdate();
    }
  } else if (parent is RenderObject) {
   // 向上层层遍历父节点,直至将最近 isRepaintBoundary 为true 的父节点添加到 _nodesNeedingPaint
    final RenderObject parent = this.parent;
    parent.markNeedsPaint();
  } else {
    // 根节点
    if (owner != null)
      owner.requestVisualUpdate();
  }
}
复制代码

由此可见, 重绘节点集合中只有 isRepaintBoundary 为 true 的 renderObject, 和 relayoutBoundary 类似,该属性决定这个RenderObject 重绘时是否独立于其父节点,如果该属性值为true ,则独立绘制,反之则一起绘制(那如果是普通节点没有加入 dirtyNodes集合, 它们怎么绘制的呢?)

(3) _repaintCompositedChild

static void _repaintCompositedChild(
  RenderObject child, {
  bool debugAlsoPaintedParent = false,
  PaintingContext childContext,
}) {
  OffsetLayer childLayer = child._layer;
   //1
  if (childLayer == null) {
    child._layer = childLayer = OffsetLayer();
  } else {
    childLayer.removeAllChildren();
  }
  //2 绘制上下文
  childContext ??= PaintingContext(child._layer, child.paintBounds);
  //3、绘制
  child._paintWithContext(childContext, Offset.zero);
  childContext.stopRecordingIfNeeded();
}
复制代码

注释1: 会先判断节点的 layer 是否为null,如果是 null, 则为改节点创建一个新的layer, 如果不为空,则需要清空该layer上的所有孩子节点(由于只有 isRepaintBoundary 的 renderObject 才在 dirtyNodes 集合中,因此不是所有的 renderObject 都拥有一个 layer) 注释2:初始化绘制的上下文 ,这个类似 canvas 上下文 注释3:调用节点的 _paintWithContext 进行绘制

(4) _paintWithContext

void _paintWithContext(PaintingContext context, Offset offset) {
    paint(context, offset);
}
复制代码

这个方法其实简单调用了 paint

(5) paint

paint 方法 是由具体的实现类去重写覆盖的, 以下面 xx 类为例

@override
void paint(PaintingContext context, Offset offset) {
  if (child != null)
    context.paintChild(child, offset);
}
复制代码

(6) paintChild

void paintChild(RenderObject child, Offset offset) {
  if (child.isRepaintBoundary) {
    _compositeChild(child, offset);
  } else {
    child._paintWithContext(this, offset);
  }
}
复制代码

绘制主要分为两种情况: 第一种情况:有绘制边界,即 isRepaintBoundary 为 true时,则重新生成图层和重绘, 往下第(7)点 第二种情况: 普通绘制,往下第 (17)点

(7)_compositeChild

void _compositeChild(RenderObject child, Offset offset) {
  if (child._needsPaint) {
    // 重绘该节点
    repaintCompositedChild(child, debugAlsoPaintedParent: true);
  } 
   // 新增图层,并将该图层 append 到 父图层
  final OffsetLayer childOffsetLayer = child._layer;
  childOffsetLayer.offset = offset;
  appendLayer(child._layer);
  }
复制代码

如果节点需要重绘,则绘制该节点,同时生成新的 layer, 并将新的 layer 加入 父图层

(8) appendLayer 这个方法调用 containerLayer 的 append 方法,将新增的图层加入父图层

@protected
void appendLayer(Layer layer) {
  layer.remove();
  _containerLayer.append(layer);
}
复制代码

(9) append 调用 adoptChild 方法设置新的 layer 的 owner 值,同时通过设置 _previousSibling 和 _nextSibling 将该图层节点加入图层链表

void append(Layer child) {
  adoptChild(child);
  child._previousSibling = lastChild;
  if (lastChild != null)
    lastChild._nextSibling = child;
  _lastChild = child;
  _firstChild ??= child;
}
复制代码

(10) adoptChild

void adoptChild(AbstractNode child) {
  child._parent = this;
  if (attached)
    child.attach(_owner);  // owner 只是一个对象,整个子树的各个节点应该拥有相同的 owner, owner
}
复制代码

(11) child.attach

@mustCallSuper
void attach(covariant Object owner) {
  _owner = owner;
}
复制代码

由上面可知, 在添加新的 layer 时,会调用子 layer 的 attach 方法将父 layer 的 owner 传给子 layer

那什么时候会重置 layer 的 owner 值呢? 在移除 子 layer 的时候

(12) removeAllChildren 在 _repaintCompositedChild 函数中,我们可以看到 layer 在重新绘制时,如果这个 layer 不为空,会先调用removeAllChildren 清空这个layer 的所有的子节点

void removeAllChildren() {
  Layer child = firstChild;
  while (child != null) {
    final Layer next = child.nextSibling;
    child._previousSibling = null;
    child._nextSibling = null;
    dropChild(child);
    child = next;
  }
  _firstChild = null;
  _lastChild = null;
}
复制代码

这个方法将 该节点 _previousSibling,_nextSibling 设置为空, 同时调用 dropChild

(13) dropChild 通过调用 图层节点的 detach 方法 重置节点的owner值

void dropChild(covariant AbstractNode child) {
  child._parent = null;
  if (attached)
    child.detach();
}
复制代码

dropChild 函数中又调用了 dettach

(14) dettach 将 owner 值设置为空

@mustCallSuper
void detach() {
  _owner = null;
}
复制代码

最初始的 owner 是什么呢?

(15) RenderView -> scheduleInitialFrame

void scheduleInitialFrame() {
  scheduleInitialLayout();
  scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
}
复制代码

(16) _updateMatricesAndCreateNewRootLayer

Layer _updateMatricesAndCreateNewRootLayer() {
  final ContainerLayer rootLayer = TransformLayer(transform: _rootTransform);
  rootLayer.attach(this);
  return rootLayer;
}
复制代码

此时 owner 指向 this, 也就是 renderView

(17)普通绘制,以 _SelectToggleButtonRenderObject 为例

void paint(PaintingContext context, Offset offset) {
  super.paint(context, offset);
  switch (textDirection) {
    case TextDirection.ltr:
        final Path leadingPath = Path()
          ..moveTo(outer.right, rrect.bottom)
          ..lineTo(rrect.left + rrect.blRadiusX, rrect.bottom)
          ..addArc(blCorner, math.pi / 2.0, sweepAngle)
          ..lineTo(rrect.left, rrect.top + rrect.tlRadiusY)
          ..addArc(tlCorner, math.pi, sweepAngle)
          ..lineTo(outer.right, rrect.top);
        context.canvas.drawPath(leadingPath, leadingPaint);
    case TextDirection.rtl:
      break;
  }
}
复制代码

使用PaintingContext的画布canvas来绘制路径等等,这里的绘制都是在一个 PictureLayer 的图层上所做的, 在初始化paintContext时,会生成一个默认的图层 PictureLayer

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


void _startRecording() {
  _currentLayer = PictureLayer(estimatedBounds);
  _recorder = ui.PictureRecorder();
  _canvas = Canvas(_recorder);
  _containerLayer.append(_currentLayer);
复制代码