7、Paint 繪製(2)

1、Flutter 之圖像繪製原理node

2、Widget、Element、RenderObjectcanvas

3、Flutter UI 更新流程bash

4、build 流程分析app

5、layout 流程分析ide

6、Paint 繪製(1)函數

8、composite 流程分析post

9、Flutter 小實踐ui

第六章節簡單介紹了 圖層,並分析了影響圖層建立的元素之一 needsCompositing, 這一章節會重點介紹整個繪製流程。this

(1) flushPaintspa

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);
  //三、繪製
  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);
複製代碼
相關文章
相關標籤/搜索