Flutter框架分析(七)-- 繪製

ad6623發表於2019-05-30

Flutter框架分析分析系列文章:

《Flutter框架分析(一)-- 總覽和Window》

《Flutter框架分析(二)-- 初始化》

《Flutter框架分析(三)-- Widget,Element和RenderObject》

《Flutter框架分析(四)-- Flutter框架的執行》

《Flutter框架分析(五)-- 動畫》

《Flutter框架分析(六)-- 佈局》

前言

本篇文章會結合Flutter原始碼給大家介紹一下渲染流水線最後一步的繪製(paint)階段。本文涉及的內容可能離大家平時開發Flutter app所需要知道的框架知識相對於前面幾章會跟遙遠一些。目前可能需要注意的地方就是RepaintBoundary這個Widget,其對應的RenderObjectRenderRepaintBoundary。這個Widget的作用在介紹完渲染流水線的繪製階段相信大家會有一個更明確的理解。

概述

我們都知道,Flutter框架中render tree負責佈局和渲染。在渲染的時候,Flutter會遍歷需要重繪的RenderObject子樹來逐一繪製。我們在螢幕上看到的Flutter app頁面其實是由不同的圖層(layers)組合(compsite)而成的。這些圖層是以樹的形式組織起來的,也就是我們在Flutter中見到的又一個比較重要的樹:layer tree。

layer tree
上圖是Flutter框架渲染機制的一個示意圖。上方綠色方框裡的內容可以認為就是本系列文章的關注所在。也就是Flutter框架渲染流水線執行的地方。可見,整個渲染流水線是執行在UI執行緒裡的,以Vsync訊號為驅動,在框架渲染完成之後會輸出layer tree。layer tree被送入engine,engine會把layer tree排程到GPU執行緒,在GPU執行緒內合成(compsite)layer tree,然後由Skia 2D渲染引擎渲染後送入GPU顯示。這裡提到layer tree是因為我們即將要分析的渲染流水線繪製階段最終輸出就是這樣的layer tree。所以繪製階段並不是簡單的呼叫paint()函式這麼簡單了,而是很多地方都涉及到layer tree的管理。

Layer

Flutter中的圖層用類Layer來代表。

abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
    
  @override
  ContainerLayer get parent => super.parent;
    
  Layer get nextSibling => _nextSibling;
  Layer _nextSibling;

  Layer get previousSibling => _previousSibling;
  Layer _previousSibling;
}
複製程式碼

Layer是個抽象類,和RenderObject一樣,繼承自AbstractNode。表明它也是個樹形結構。屬性parent代表其父節點,型別是ContainerLayer。這個類繼承自Layer。只有ContainerLayer型別及其子類的圖層可以擁有孩子,其他型別的Layer子類都是葉子圖層。nextSiblingpreviousSibling表示同一圖層的前一個和後一個兄弟節點,也就是圖層孩子節點們是用雙向連結串列儲存的。

class ContainerLayer extends Layer {
  Layer _firstChild;
  Layer _lastChild;
  
  void append(Layer child) {
    adoptChild(child);
    child._previousSibling = lastChild;
    if (lastChild != null)
      lastChild._nextSibling = child;
    _lastChild = child;
    _firstChild ??= child;
  }

  void _removeChild(Layer child) {
    if (child._previousSibling == null) {
      _firstChild = child._nextSibling;
    } else {
      child._previousSibling._nextSibling = child.nextSibling;
    }
    if (child._nextSibling == null) {
      _lastChild = child.previousSibling;
    } else {
      child.nextSibling._previousSibling = child.previousSibling;
    }
    child._previousSibling = null;
    child._nextSibling = null;
    dropChild(child);
  }

  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;
  }
    
}
複製程式碼

ContainerLayer增加了頭和尾兩個孩子節點屬性,並提供了新增及刪除孩子節點的方法。

ContainerLayer的子類有OffsetLayer,ClipRectLayer等等。

葉子型別的圖層有TextureLayer,PlatformViewLayer, PerformanceOverlayLayerPictureLayer等等,框架中大部分RenderObject的繪製的目標圖層都是PictureLayer

class PictureLayer extends Layer {

    final Rect canvasBounds;
    
    ui.Picture _picture;
}
複製程式碼

屬性canvasBounds代表圖層畫布的邊界,但這個屬性是建議性質的。 屬性picture來自dart:ui庫。

分析

回到我們熟悉的drawFrame()函式中,pipelineOwner.flushLayout()呼叫完成以後渲染流水線就進入了繪製(paint)階段。

void drawFrame() {
  pipelineOwner.flushLayout();
  pipelineOwner.flushCompositingBits();
  pipelineOwner.flushPaint();
  renderView.compositeFrame(); // this sends the bits to the GPU
  pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}

複製程式碼

繪製階段的第一個呼叫是pipelineOwner.flushCompositingBits()

pipelineOwner.flushCompositingBits()

這個呼叫是用來更新render tree 中RenderObject_needsCompositing標誌位的。

在介紹這個呼叫之前我們,我們先來了解一些RenderObject的標誌位。

bool _needsCompositing:標誌自身或者某個孩子節點有合成層(compositing layer)。如果當前節點需要合成,那麼所有祖先節點也都需要合成。

bool _needsCompositingBitsUpdate:標誌當前節點是否需要更新_needsCompositing。這個標誌位由下方的markNeedsCompositingBitsUpdate()函式設定。

bool get isRepaintBoundary => false;:標誌當前節點是否與父節點分開來重繪。當這個標誌位為true的時候,父節點重繪的時候子節點不一定也需要重繪,同樣的,當自身重繪的時候父節點不一定需要重繪。此標誌位為trueRenderObject有render tree的根節點RenderView,有我們熟悉的RenderRepaintBoundaryTextureBox等。

bool get alwaysNeedsCompositing => false;:標誌當前節點是否總是需要合成。這個標誌位為true的話意味著當前節點繪製的時候總是會新開合成層(composited layer)。例如TextureBox, 以及我們熟悉的顯示執行時效能的RenderPerformanceOverlay等。

在渲染流水線的構建階段,有些情況下render tree裡的節點需要重新更新_needsCompositing,比如說render tree裡節點的增加,刪除。這個標記工作由函式markNeedsCompositingBitsUpdate()完成。

void markNeedsCompositingBitsUpdate() {
    if (_needsCompositingBitsUpdate)
      return;
    _needsCompositingBitsUpdate = true;
    if (parent is RenderObject) {
      final RenderObject parent = this.parent;
      if (parent._needsCompositingBitsUpdate)
        return;
      if (!isRepaintBoundary && !parent.isRepaintBoundary) {
        parent.markNeedsCompositingBitsUpdate();
        return;
      }
    }
    if (owner != null)
      owner._nodesNeedingCompositingBitsUpdate.add(this);
  }
複製程式碼

這個呼叫會從當前節點往上找,把所有父節點的_needsCompositingBitsUpdate標誌位都置位true。直到自己或者父節點的isRepaintBoundarytrue。最後會把自己加入到PipelineOwner_nodesNeedingCompositingBitsUpdate列表裡面。而函式呼叫pipelineOwner.flushCompositingBits()正是用來處理這個列表的。

flushCompositingBits()原始碼如下:

void flushCompositingBits() {
    
    _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
    for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
      if (node._needsCompositingBitsUpdate && node.owner == this)
        node._updateCompositingBits();
    }
    _nodesNeedingCompositingBitsUpdate.clear();
  }
複製程式碼

首先把列表_nodesNeedingCompositingBitsUpdate按照節點在樹中的深度排序。然後遍歷呼叫node._updateCompositingBits()

  void _updateCompositingBits() {
    if (!_needsCompositingBitsUpdate)
      return;
    final bool oldNeedsCompositing = _needsCompositing;
    _needsCompositing = false;
    visitChildren((RenderObject child) {
      child._updateCompositingBits();
      if (child.needsCompositing)
        _needsCompositing = true;
    });
    if (isRepaintBoundary || alwaysNeedsCompositing)
      _needsCompositing = true;
    if (oldNeedsCompositing != _needsCompositing)
      markNeedsPaint();
    _needsCompositingBitsUpdate = false;
  }
複製程式碼

這裡做的事情是從當前節點往下找,如果某個子節點isRepaintBoundarytruealwaysNeedsCompositingtrue則設定_needsCompositingtrue。子節點這個標誌位為true的話,那麼父節點的該標誌位也會被設定為true。如果_needsCompositing發生了變化,那麼會呼叫markNeedsPaint()通知渲染流水線本RenderObject需要重繪了。為啥要重繪呢?原因是本``RenderObject`所在的圖層(layer)可能發生了變化。

pipelineOwner.flushPaint()

函式flushPaint()處理的是之前加入到列表_nodesNeedingPaint裡的節點。當某個RenderObject需要被重繪的時候會呼叫markNeedsPaint()

void markNeedsPaint() {
    if (_needsPaint)
      return;
    _needsPaint = true;
    if (isRepaintBoundary) {
      if (owner != null) {
        owner._nodesNeedingPaint.add(this);
        owner.requestVisualUpdate();
      }
    } else if (parent is RenderObject) {
      final RenderObject parent = this.parent;
      parent.markNeedsPaint();
    } else {
      if (owner != null)
        owner.requestVisualUpdate();
    }
  }
複製程式碼

函式markNeedsPaint()首先做的是把自己的標誌位_needsPaint設定為true。然後會向上查詢最近的一個isRepaintBoundarytrue的祖先節點。直到找到這樣的節點,才會把這個節點加入到_nodesNeedingPaint列表中,也就是說,並不是任意一個需要重繪的RenderObject就會被加入這個列表,而是往上找直到找到最近的一個isRepaintBoundarytrue才會放入這個列表,換句話說,這個列表裡只有isRepaintBoundarytrue這種型別的節點。也就是說重繪的起點是從“重繪邊界”開始的。

  void flushPaint() {
    try {
      final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
      _nodesNeedingPaint = <RenderObject>[];
      // Sort the dirty nodes in reverse order (deepest first).
      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();
          }
        }
      }
    } finally {
      ...
    }
  }
複製程式碼

在處理需要重繪的節點的時候,會先給這些節點做個排序,這裡需要注意的是,和之前flushLayout()裡的排序不同,這裡的排序是深度度深的節點在前。在迴圈體裡,會判斷當前節點的_layer屬性是否處於attached的狀態。如果_layer.attachedtrue的話呼叫PaintingContext.repaintCompositedChild(node);去做繪製,否則的話呼叫node._skippedPaintingOnLayer()將自身以及到上層繪製邊界之間的節點的_needsPaint全部置為true。這樣在下次_layer.attached變為true的時候會直接繪製。

從上述程式碼也可以看出,重繪邊界相當於把Flutter的繪製做了分塊處理,重繪的從上層重繪邊界開始,到下層重繪邊界為止,在此之間的RenderObject都需要重繪,而邊界之外的就可能不需要重繪,這也是一個效能上的考慮,儘量避免不必要的繪製。所以如何合理安排RepaintBoundary是我們在做Flutter app的效能優化時候需要考慮的一個方向。

這裡的_layer屬性就是我們之前說的圖層,這個屬性只有繪製邊界的RenderObject才會有值。一般的RenderObject這個屬性是null

  static void _repaintCompositedChild(
    RenderObject child, {
    bool debugAlsoPaintedParent = false,
    PaintingContext childContext,
  }) {
    if (child._layer == null) {
      child._layer = OffsetLayer();
    } else {
      child._layer.removeAllChildren();
    }
    childContext ??= PaintingContext(child._layer, child.paintBounds);
    child._paintWithContext(childContext, Offset.zero);
    childContext.stopRecordingIfNeeded();
  }
複製程式碼

函式_repaintCompositedChild()會先檢查RenderObject的圖層屬性,為空則新建一個OffsetLayer例項。如果圖層已經存在的話就把孩子清空。

如果沒有PaintingContext的話會新建一個,然後讓開始繪製。我們先來看一下PaintingContext這個類:

class PaintingContext extends ClipContext {
  @protected
  PaintingContext(this._containerLayer, this.estimatedBounds)

  final ContainerLayer _containerLayer;
  
  final Rect estimatedBounds;
  
  PictureLayer _currentLayer;
  ui.PictureRecorder _recorder;
  Canvas _canvas;

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

  void _startRecording() {
    _currentLayer = PictureLayer(estimatedBounds);
    _recorder = ui.PictureRecorder();
    _canvas = Canvas(_recorder);
    _containerLayer.append(_currentLayer);
  }
  
   void stopRecordingIfNeeded() {
    if (!_isRecording)
      return;
    _currentLayer.picture = _recorder.endRecording();
    _currentLayer = null;
    _recorder = null;
    _canvas = null;
  }
複製程式碼

PaintingContext字面意思是繪製上下文,其屬性_containerLayer是容器圖層,來自構造時的入參。也就是說PaintingContext是和容器圖層關聯的。接下來還有PictureLayer型別的_currentLayer屬性, ui.PictureRecorder型別的_recorder屬性和我們熟悉的Canvas型別的屬性_canvas。函式_startRecording() 例項化了這幾個屬性。_recorder用來錄製繪製命令,_canvas繫結一個錄製器。最後,_currentLayer會作為子節點加入到_containerLayer中。有開始那麼就會有結束,stopRecordingIfNeeded()用來結束當前繪製的錄製。結束時會把繪製完畢的Picture賦值給當前的PictureLayer.picture

有了PaintingContext以後,就可以呼叫RenderObject._paintWithContext()開始繪製了,這個函式會直接呼叫到我們熟悉的RenderObject.paint(context, offset),我們知道函式paint()RenderObject子類自己實現。從之前的原始碼分析我們知道繪製起點都是“繪製邊界”。這裡我們就拿我們熟悉的一個“繪製邊界”,RenderRepaintBoundary,為例來走一下繪製流程,它的繪製函式的實現在RenderProxyBoxMixin類中:

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null)
      context.paintChild(child, offset);
  }
複製程式碼

這個呼叫又回到了PaintingContextpaintChild()方法:

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

這裡會檢查子節點是不是繪製邊界,如果不是的話,就是普通的繪製了,接著往下呼叫_paintWithContext(),繼續往當前的PictureLayer上繪製。如果是的話就把當前的繪製先停掉。然後呼叫_compositeChild(child, offset);

  void _compositeChild(RenderObject child, Offset offset) {
    if (child._needsPaint) {
      repaintCompositedChild(child, debugAlsoPaintedParent: true);
    }
    child._layer.offset = offset;
    appendLayer(child._layer);
  }
複製程式碼

如果這個子繪製邊界被標記為需要重繪的話,那麼就呼叫repaintCompositedChild()來重新生成圖層然後重繪。如果這個子繪製邊界沒有被標記為需要重繪的話,就跳過了重新生成圖層和重繪。最後只需要把子圖層加入到當前容器圖層中就行了。

上面說的是子節點是繪製邊界的時候的繪製流程,那如果子節點是普通的一個RenderObject呢?這裡就拿Flutter app出錯控制元件的繪製做個例子:

void paint(PaintingContext context, Offset offset) {
    try {
      context.canvas.drawRect(offset & size, Paint() .. color = backgroundColor);
      double width;
      if (_paragraph != null) {
        // See the comment in the RenderErrorBox constructor. This is not the
        // code you want to be copying and pasting. :-)
        if (parent is RenderBox) {
          final RenderBox parentBox = parent;
          width = parentBox.size.width;
        } else {
          width = size.width;
        }
        _paragraph.layout(ui.ParagraphConstraints(width: width));

        context.canvas.drawParagraph(_paragraph, offset);
      }
    } catch (e) {
      // Intentionally left empty.
    }
  }
複製程式碼

這看起來就像個正常的繪製了,我們會用來自PaintingContext的畫布canvas來繪製矩形,繪製文字等等。從前面的分析也可以看出,這裡的繪製都是在一個PictureLayer的圖層上所做的。

至此 pipelineOwner.flushPaint();這個函式的呼叫就跑完了,通過分析我們可以知道,繪製工作其實主要是在這個函式中完成的。接下來我們再來看一下繪製流程的最後一個重要的函式呼叫:

renderView.compositeFrame()

這裡的renderView就是我們之前說的render tree的根節點。這個函式呼叫主要是把整個layer tree生成scene送到engine去顯示。

void compositeFrame() {
    try {
      final ui.SceneBuilder builder = ui.SceneBuilder();
      final ui.Scene scene = layer.buildScene(builder);
      if (automaticSystemUiAdjustment)
        _updateSystemChrome();
      _window.render(scene);
      scene.dispose();
    } finally {
      Timeline.finishSync();
    }
  }
複製程式碼

ui.SceneBuilder()最終呼叫Native方法SceneBuilder_constructor。也就是說ui.SceneBuilder例項是由engine建立的。接下來就是呼叫layer.buildScene(builder)方法,這個方法會返回一個ui.Scene例項。由於方法compositeFrame()的呼叫者是renderView。所以這裡這個layer是來自renderView的屬性,我們前面說過只有繪製邊界節點才有layer。所以可見render tree的根節點renderView也是一個繪製邊界。那麼這個layer是從哪裡來的呢?在文章《Flutter框架分析(二)-- 初始化》我們講過,框架初始化的過程中renderView會排程開天闢地的第一幀:

void scheduleInitialFrame() {
    scheduleInitialLayout();
    scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
    owner.requestVisualUpdate();
  }
  
  Layer _updateMatricesAndCreateNewRootLayer() {
    _rootTransform = configuration.toMatrix();
    final ContainerLayer rootLayer = TransformLayer(transform: _rootTransform);
    rootLayer.attach(this);
    return rootLayer;
  }
  
  void scheduleInitialPaint(ContainerLayer rootLayer) {
    _layer = rootLayer;
    owner._nodesNeedingPaint.add(this);
  }

複製程式碼

在方法_updateMatricesAndCreateNewRootLayer()中,我們看到這裡例項化了一個TransformLayerTransformLayer繼承自OffsetLayer。構造時需要傳入Matrix4型別的引數transform。這個Matrix4其實和我們在Android中見到的Matrix是一回事。代表著矩陣變換。這裡的transform來自我們之前講過的ViewConfiguration,它就是把裝置畫素比例轉化成了矩陣的形式。最終這個layer關聯上了renderView。所以這裡這個TransformLayer其實也是layer tree的根節點了。

回到我們的繪製流程。layer.buildScene(builder);這個呼叫我們自然是去 TransformLayer裡找了,但這個方法是在其父類OffsetLayer內,從這個呼叫開始就都是對圖層進行操作,最終把layer tree轉換為場景scene

ui.Scene buildScene(ui.SceneBuilder builder) {
    List<PictureLayer> temporaryLayers;
    updateSubtreeNeedsAddToScene();
    addToScene(builder);
    final ui.Scene scene = builder.build();
    return scene;
  }
複製程式碼

函式呼叫updateSubtreeNeedsAddToScene();會遍歷layer tree來設定_subtreeNeedsAddToScene標誌位,如果有任意子圖層的新增、刪除操作,則該子圖層及其祖先圖層都會被置上_subtreeNeedsAddToScene標誌位。然後會呼叫addToScene(builder);

  @override
   @override
  ui.EngineLayer addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
    _lastEffectiveTransform = transform;
    final Offset totalOffset = offset + layerOffset;
    if (totalOffset != Offset.zero) {
      _lastEffectiveTransform = Matrix4.translationValues(totalOffset.dx, totalOffset.dy, 0.0)
        ..multiply(_lastEffectiveTransform);
    }
    builder.pushTransform(_lastEffectiveTransform.storage);
    addChildrenToScene(builder);
    builder.pop();
    return null; // this does not return an engine layer yet.
  }
複製程式碼

builder.pushTransform會呼叫到engine層。相當於告訴engine這裡我要加一個變換圖層。然後呼叫ddChildrenToScene(builder)將子圖層加入場景中,完了還要把之前壓棧的變換圖層出棧。

void addChildrenToScene(ui.SceneBuilder builder, [ Offset childOffset = Offset.zero ]) {
    Layer child = firstChild;
    while (child != null) {
      if (childOffset == Offset.zero) {
        child._addToSceneWithRetainedRendering(builder);
      } else {
        child.addToScene(builder, childOffset);
      }
      child = child.nextSibling;
    }
  }
複製程式碼

這就是遍歷新增子圖層的呼叫。主要還是逐層向下的呼叫addToScene()。這個方法不同的圖層會有不同的實現,對於容器類圖層而言,主要就是做三件事:1.新增自己圖層的效果然後入棧,2.新增子圖層,3. 出棧。

在所有圖層都處理完成之後。回到renderView.compositeFrame(),可見最後會把處理完得到的場景通過_window.render(scene);呼叫送入engine去顯示了。

至此渲染流水線的繪製(paint)階段就算是跑完了。

等等,好像缺了點什麼,在分析繪製的過程中我們看到有個主要的呼叫pipelineOwner.flushCompositingBits()是在更新render tree裡節點的_needsCompositing標誌位的。但是我們這都把流程說完了,貌似沒有看到這個標誌位在哪裡用到啊。這個標誌位肯定在哪裡被用到了,否則我們費這麼大勁更新有啥用呢?回去再研究一下程式碼......

這個標誌位某些RenderObject在其paint()函式中會用到,作用呢,就體現在PaintingContext的這幾個函式的呼叫上了:

  void pushClipRect(bool needsCompositing, Offset offset, Rect clipRect, PaintingContextCallback painter, { Clip clipBehavior = Clip.hardEdge }) {
    final Rect offsetClipRect = clipRect.shift(offset);
    if (needsCompositing) {
      pushLayer(ClipRectLayer(clipRect: offsetClipRect, clipBehavior: clipBehavior), painter, offset, childPaintBounds: offsetClipRect);
    } else {
      clipRectAndPaint(offsetClipRect, clipBehavior, offsetClipRect, () => painter(this, offset));
    }
  }

  void pushClipRRect(bool needsCompositing, Offset offset, Rect bounds, RRect clipRRect, PaintingContextCallback painter, { Clip clipBehavior = Clip.antiAlias }) {
    final Rect offsetBounds = bounds.shift(offset);
    final RRect offsetClipRRect = clipRRect.shift(offset);
    if (needsCompositing) {
      pushLayer(ClipRRectLayer(clipRRect: offsetClipRRect, clipBehavior: clipBehavior), painter, offset, childPaintBounds: offsetBounds);
    } else {
      clipRRectAndPaint(offsetClipRRect, clipBehavior, offsetBounds, () => painter(this, offset));
    }
  }

  
  void pushClipPath(bool needsCompositing, Offset offset, Rect bounds, Path clipPath, PaintingContextCallback painter, { Clip clipBehavior = Clip.antiAlias }) {
    final Rect offsetBounds = bounds.shift(offset);
    final Path offsetClipPath = clipPath.shift(offset);
    if (needsCompositing) {
      pushLayer(ClipPathLayer(clipPath: offsetClipPath, clipBehavior: clipBehavior), painter, offset, childPaintBounds: offsetBounds);
    } else {
      clipPathAndPaint(offsetClipPath, clipBehavior, offsetBounds, () => painter(this, offset));
    }
  }

  void pushTransform(bool needsCompositing, Offset offset, Matrix4 transform, PaintingContextCallback painter) {
    final Matrix4 effectiveTransform = Matrix4.translationValues(offset.dx, offset.dy, 0.0)
      ..multiply(transform)..translate(-offset.dx, -offset.dy);
    if (needsCompositing) {
      pushLayer(
        TransformLayer(transform: effectiveTransform),
        painter,
        offset,
        childPaintBounds: MatrixUtils.inverseTransformRect(effectiveTransform, estimatedBounds),
      );
    } else {
      canvas
        ..save()
        ..transform(effectiveTransform.storage);
      painter(this, offset);
      canvas
        ..restore();
    }
  }
複製程式碼

needsCompositing作為這幾個函式的入參,從程式碼可見其作用主要是控制這幾種特殊的繪製操作的具體實現方式,如果needsCompositingtrue的話,則會呼叫pushLayer,引數我們之前見過的各種圖層

  void pushLayer(ContainerLayer childLayer, PaintingContextCallback painter, Offset offset, { Rect childPaintBounds }) {
    stopRecordingIfNeeded();
    appendLayer(childLayer);
    final PaintingContext childContext = createChildContext(childLayer, childPaintBounds ?? estimatedBounds);
    painter(childContext, offset);
    childContext.stopRecordingIfNeeded();
  }

  @protected
  PaintingContext createChildContext(ContainerLayer childLayer, Rect bounds) {
    return PaintingContext(childLayer, bounds);
  }
複製程式碼

流程基本上和我們之前看到的重繪的時候新增一個圖層的操作是一樣的。

而如果needsCompositingfalse的話則走的是canvas的各種變換了。大家感興趣的話可以去看一下原始碼,這裡就不細說了。

總結

至此Flutter框架渲染流水線的繪製(paint)階段就分析完了。繪製流程並不像之前的構建,佈局流程那樣直接,只要遍歷element tree或者render tree就行了。渲染階段會出現另一個樹,圖層樹,layer tree。整個繪製流程就是在把render tree轉化為適合的layer tree,最後再生成場景(scene)的一個過程。

最後,在瞭解渲染過程的基礎上,推薦大家再看一下這個來自Google工程師的視訊:深入瞭解 Flutter 的高效能圖形渲染。相信大家在看過這個視訊之後,會對Flutter框架的渲染,以及可能遇到的一些效能問題會有進一步的理解。

相關文章