Flutter的繪製流程簡述

相對於React NativeWeex等跨平臺框架,Flutter擁有本身的UI繪製體系,避免了React NativeWeex等跨平臺框架與Native系統的橋接,從而更好的提高了性能。html

Flutter中,UI都是一幀一幀的繪製,但這繪製的背後都會通過以下階段。node

  1. 動畫與微任務階段,主要是處理動畫及執行一系列微任務。
  2. 構建階段(build),找出標記爲「髒」的節點與佈局邊界之間的全部節點,並作相應的更新。
  3. 佈局階段,計算Widget的大小及位置的肯定。
  4. compositingBits階段,重繪以前的預處理操做,檢查RenderObject是否須要重繪。
  5. 繪製階段,根據Widget大小及位置來繪製UI。
  6. compositing階段,將UI數據發送給GPU處理。
  7. semantics階段,與平臺的輔助功能相關。
  8. finalization階段,主要是從Element樹中移除無用的Element對象及處理繪製結束回調。

下面就來分析上述的各個階段git

一、動畫與微任務階段

該階段主要是處理動畫及微任務。先來看動畫的處理,在使用動畫時,不少時候都會添加一個回調函數來進行狀態獲取或數據更新,如經過addListeneraddStatusListener等函數來添加,而這些回調函數就會在本階段來執行。具體是在SchedulerBinding中的handleBeginFrame函數中實現。github

void handleBeginFrame(Duration rawTimeStamp) {
    ...
    try {
      // TRANSIENT FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.transientCallbacks;
      //切換爲transientCallbacks階段
      final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
      //清空已註冊的回調函數
      _transientCallbacks = <int, _FrameCallbackEntry>{};
      //遍歷全部註冊的回調方法
      callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
        if (!_removedIds.contains(id))
          //執行已經註冊的回調函數
          _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
      });
      _removedIds.clear();
    } finally {
      //切換爲midFrameMicrotasks階段
      _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
    }
  }
複製代碼

_invokeFrameCallback就會調用在使用動畫時註冊的回調函數,這裏僅執行一次。若是咱們在運行時調用_invokeFrameCallback函數的代碼註釋調,那麼就沒法獲取動畫的狀態,從而影響動畫的正確執行。緩存

當回調函數執行完畢後,就會進入微任務階段,在該階段會執行一系列微任務,因爲這涉及到Flutter的異步任務體系,所以這裏就再也不敘述。數據結構

二、build階段

在上一階段執行完畢後,就進入build階段,在該階段主要是從新構建標記爲「髒」的Widget節點及將須要更新的RenderObject對象標記爲「髒」。app

handleBeginFrame函數執行完畢後,就會執行handleDrawFrame函數,該函數在SchedulerBinding對象初始化時會與Window相關聯,因此除第一次須要主動調用外,其餘時候皆是經過Window來調用該函數。框架

void handleDrawFrame() {
    assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
    Timeline.finishSync(); // end the "Animate" phase
    try {
      //持久幀回調,該回調會一直存在,不會移除
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);

      //當前幀繪製完成回調
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
      final List<FrameCallback> localPostFrameCallbacks =
          List<FrameCallback>.from(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      //當執行這裏時,表明當前幀已經繪製完畢
      for (FrameCallback callback in localPostFrameCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);
    } finally {
      //進入空閒狀態
      _schedulerPhase = SchedulerPhase.idle;
      Timeline.finishSync(); // end the Frame
      profile(() {
        _profileFrameStopwatch.stop();
        _profileFramePostEvent();
      });
      _currentFrameTimeStamp = null;
    }
  }
複製代碼

這裏重點關注持久幀回調,該回調也是UI繪製的關鍵函數,是在RendererBinding對象初始化時註冊的。異步

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    ...
    //註冊持久幀回調
    addPersistentFrameCallback(_handlePersistentFrameCallback);
  }
  void _handlePersistentFrameCallback(Duration timeStamp) {
    //繪製幀
    drawFrame();
  }
  //繪製幀
  void drawFrame() {
    //對Widget進行測量、佈局
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    //對Widget進行繪製
    pipelineOwner.flushPaint();
    //發送數據給GPU
    renderView.compositeFrame(); // this sends the bits to the GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
  }
}
複製代碼

根據函數名能夠發現並無發現關於構建Widget的相關函數,那麼在什麼時候構建尼?經過查看源碼能夠發現,在WidgetsBinding中重寫了drawFrame函數。在該函數中會建立新的Widget對象替換舊的Widget對象並將不須要的Element節點從樹中移除。ide

@override
  void drawFrame() {
    ...
    try {
      //若是根結點存在,就從新構建Widget
      if (renderViewElement != null)
        buildOwner.buildScope(renderViewElement);
      //調用RendererBinding中的drawFrame函數
      super.drawFrame();
      //移除再也不使用的Element節點
      buildOwner.finalizeTree();
    } finally {...}
    ...
  }
複製代碼

2.一、從新build Widget對象

Widget對象的建立是在buildScope()函數中實現的,這是一個很是重要的函數,具體實現以下。

void buildScope(Element context, [ VoidCallback callback ]) {
    if (callback == null && _dirtyElements.isEmpty)
      return;
    try {
      //「髒」節點列表須要從新排序
      _scheduledFlushDirtyElements = true;
      ...
      //將標記爲「髒」的Element節點根據深度進行排序
      _dirtyElements.sort(Element._sort);
      _dirtyElementsNeedsResorting = false;
      //標記爲「髒」的Element節點數量
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      //遍歷「髒」節點
      while (index < dirtyCount) {
        try {
          //從新構建Widget,及是否複用當前Element
          _dirtyElements[index].rebuild();
        } catch (e, stack) {
          ...
        }
        index += 1;
        //當_dirtyElements集合中的「髒」節點還未處理完畢時,又添加了新的「髒」節點
        if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
          //根據「髒」節點的深度進行排序
          _dirtyElements.sort(Element._sort);
          _dirtyElementsNeedsResorting = false;
          dirtyCount = _dirtyElements.length;
          //若是當前節點的深度比新加入的「髒」節點深度要深,則須要將處理座標指向新加入的「髒」節點
          while (index > 0 && _dirtyElements[index - 1].dirty) {
            index -= 1;
          }
        }
      }
    } finally {
      //清除_dirtyElements中全部節點的「髒」狀態
      for (Element element in _dirtyElements) {
        element._inDirtyList = false;
      }
      _dirtyElements.clear();
      _scheduledFlushDirtyElements = false;
      _dirtyElementsNeedsResorting = null;
      Timeline.finishSync();
    }
  }
複製代碼

_dirtyElements是一個集合,存儲了全部標記爲「髒」的節點。在對其中的「髒」節點進行處理時,須要首先對集合中的「髒」節點進行排序,其排序規則以下。

  • 若是「髒」節點的深度不一樣,則按照深度進行升序排序
  • 若是「髒」節點的深度相同,則會將「髒」節點放在集合的右側,「乾淨」節點則在在集合的左側。

在排序完成後,就要遍歷該集合,對其中的「髒」節點進行處理。在這裏調用的是rebuild函數,經過該函數,會從新建立「髒」節點下的全部Widget對象,並根據新的Widget對象來判斷是否須要重用Element對象。通常只要不是增刪WidgetElement對象都會被重用,從而也就會重用RenderObject對象。因爲Widget是一個很是輕量級的數據結構,因此在UI更新時作到了把性能損耗降到最低。

這裏要注意一點的是,若是_dirtyElements中的「髒」節點還未處理完畢,就又新增了「髒」節點,那麼這時候就會從新排序,保證_dirtyElements集合的左側永遠是「乾淨」節點,右側永遠是「髒」節點。

因爲rebuild函數比較重要,這裏就重點介紹一下該函數,在rebuild函數中會調用performRebuild函數,該函數是一個抽象函數,在其子類實現,而標記爲「髒」的Element都是StatefulElement。因此就來StatefulElement或者其父類中查找performRebuild函數。

abstract class ComponentElement extends Element {
  ...
  @override
  void performRebuild() {
    Widget built;
    try {
      //從新建立新的`Widget`對象
      built = build();
      debugWidgetBuilderValue(widget, built);
    } catch (e, stack) {
      //當構建Widget對象出錯時展現的默認頁面,能夠修改該頁面來使異常界面更友好的顯示
      built = ErrorWidget.builder(_debugReportException('building $this', e, stack));
    } finally {
      //清除「髒」標記
      _dirty = false;
    }
    try {
      //更新子Element對應的Widget對象
      _child = updateChild(_child, built, slot);
      assert(_child != null);
    } catch (e, stack) {
      //當構建Widget對象出錯時展現的默認頁面
      built = ErrorWidget.builder(_debugReportException('building $this', e, stack));
      _child = updateChild(null, built, slot);
    }
  }
}
複製代碼

performRebuild函數作的事很簡單,就是建立新的Widget對象來替換舊的對象。上面的build函數調用的就是State類中的build函數,而後再調用ElementupdateChild函數,該函數在Flutter之Widget層級介紹中進行了簡單的介紹,就是更新Element對應的Widget對象。而在updateChild函數中又會調用子Elementupdate函數,從而調用子ElementperformRebuild,而後在調用子ElementupdateChildupdate函數。以此類推,從而更新其全部子ElementWidget對象。

最後就是調用葉子節點的 updateRenderObject函數來更新 RenderObject。在更新 RenderObject對象時,會根據狀況來對須要從新佈局及從新繪製的 RenderObject對象進行標記。而後等待下一次的Vsync信號時來從新佈局及繪製UI。

2.二、標記RenderObject

對於RenderObject對象,能夠經過markNeedsLayoutmarkNeedsPaint來標記是否須要從新佈局及從新繪製。但在當前階段只會調用markNeedsLayout來標記須要從新佈局的RenderObject對象,在下一階段纔會標記須要從新繪製的RenderObject,因此先來看markNeedsLayout函數。

void markNeedsLayout() {
    ...
    //判斷佈局邊界是不是是當前RenderObject對象
    if (_relayoutBoundary != this) {
      markParentNeedsLayout();
    } else {
      _needsLayout = true;
      if (owner != null) {
        //標記當前RenderObject及其子RenderObject對象須要從新佈局
        //將當前`RenderObject`添加到集合中。
        owner._nodesNeedingLayout.add(this);
        owner.requestVisualUpdate();
      }
    }
  }
  @protected
  void markParentNeedsLayout() {
    _needsLayout = true;
    final RenderObject parent = this.parent;
    if (!_doingThisLayoutWithCallback) {
      //調用父類的markNeedsLayout函數
      parent.markNeedsLayout();
    } else {
      assert(parent._debugDoingThisLayout);
    }
    assert(parent == this.parent);
  }
複製代碼

markNeedsLayout函數的代碼實現很簡單,就是不斷遍歷父RenderObject對象,從而找到佈局邊界的RenderObject對象,並將該RenderObject對象添加到集合_nodesNeedingLayout中,而後在下一階段就從該對象開始佈局。

在這裏有個「佈局邊界」的概念,在Flutter中,能夠給任意節點設置佈局邊界,即當邊界內的任何對象發生從新佈局時,不會影響邊界外的對象,反之亦然。

在從新構建build函數及標記RenderObject完成後,就進入下一階段,開始佈局。

三、layout階段

在該階段,會肯定每一個組件的大小及位置,至關於Android中的onMeasure+onLayout函數所實現的功能。若是是第一次調用該函數,該階段就會遍歷全部的組件,來肯定其大小及位置;不然該階段就會遍歷佈局邊界內的全部組件,來肯定其大小及位置。

當上一階段中的buildOwner.buildScope(renderViewElement)函數執行完畢後,就會調用RendererBindingdrawFrame函數,該函數實現很是簡潔。

//繪製幀
  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.
  }
複製代碼

其中flushLayout就是進行組件的大小及位置肯定,在該函數中會遍歷集合_nodesNeedingLayout並調用集合中每一個對象的_layoutWithoutResize函數。

void flushLayout() {
    try {
      while (_nodesNeedingLayout.isNotEmpty) {
        final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
        _nodesNeedingLayout = <RenderObject>[];
        for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
          //調用RenderObject對象的_layoutWithoutResize函數
          if (node._needsLayout && node.owner == this)
            node._layoutWithoutResize();
        }
      }
    } finally {...}
  }
複製代碼

_layoutWithoutResize函數是私有的,因此不存在重寫的問題。那麼就直接來看該函數。

void _layoutWithoutResize() {
    try {
      performLayout();
      markNeedsSemanticsUpdate();
    } catch (e, stack) {...}
    _needsLayout = false;
    markNeedsPaint();
  }
複製代碼

_layoutWithoutResize函數很簡單,就直接調用了performLayout函數。

因爲performLayout是一個抽象函數,須要在子類重寫,但都會在該函數中調用layout函數,而後又在layout函數中調用performLayout函數。以此類推,從而肯定更新UI部分的組件大小及位置,整體流程以下。

固然, RenderObject對象的size也不是隨便肯定的,由於在調用 RenderObjectlayout函數時,會傳遞一個繼承自 Constraints的對象。該對象是一個佈局約束,由父傳給子,子會根據該對象來決定本身的大小。

3.一、標記RenderObject

當大小及位置肯定後,就又會對RenderObject進行一次標記,此次跟上一階段的標記大同小異,但此次是標記可繪製的RenderObject對象,而後在後面對這些對象進行從新繪製。標記可繪製的RenderObject對象是經過markNeedsPaint函數來實現的,代碼以下。

void markNeedsPaint() {
    if (_needsPaint)
      return;
    _needsPaint = true;
    if (isRepaintBoundary) {
      //標記須要從新繪製的RenderObject對象
      //須要繪製當前圖層
      if (owner != null) {
        owner._nodesNeedingPaint.add(this);
        owner.requestVisualUpdate();
      }
    } else if (parent is RenderObject) {
      //沒有本身的圖層,與父類共用同一圖層
      final RenderObject parent = this.parent;
      //遍歷其父RenderObject對象
      parent.markNeedsPaint();
    } else {
      //當是RenderView時,須要本身建立新的圖層
      if (owner != null)
        owner.requestVisualUpdate();
    }
  }
複製代碼

markNeedsPaint函數中涉及到了一個「重繪邊界」的概念。在進入和走出重繪邊界時,Flutter會強制切換新的圖層,這樣就能夠避免邊界內外的互相影響。固然重繪邊界也能夠在任何節點手動設置,可是通常不須要咱們來實現,Flutter提供的控件默認會在須要設置的地方自動設置。

四、compositingBits階段

在組件的大小及位置肯定後,就會進入當前階段。該階段主要是作一件事,就是將RenderObject樹上新增及刪除的RenderObject對象標記爲「髒」,方便在下一階段對這些RenderObject對象進行重繪。具體代碼實現是在flushCompositingBits函數中,該函數在Layout階段後當即調用。

void flushCompositingBits() {
    ...
    //將RenderObject對象按照深度進行排序
    _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
    for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
      if (node._needsCompositingBitsUpdate && node.owner == this)
        //將RenderObject對象及其子對象標記爲「髒」
        node._updateCompositingBits();
    }
    _nodesNeedingCompositingBitsUpdate.clear();
    ...
  }
複製代碼

_nodesNeedingCompositingBitsUpdate是一個集合,只有RenderObject對象的_needsCompositing爲true時,纔會添加到該集合中。在RenderObject對象建立時,_needsCompositing的值會根據isRepaintBoundaryalwaysNeedsCompositing來共同判斷。

RenderObject() {
    //isRepaintBoundary決定當前RenderObject是否與父RenderObject分開繪製,默認爲false,其值在當前對象的生命週期內沒法修改。也就是判斷當前對象是不是繪製邊界
    //alwaysNeedsCompositing爲true表示當前RenderObject會一直重繪,如視頻播放,默認爲false
    _needsCompositing = isRepaintBoundary || alwaysNeedsCompositing;
  }
複製代碼

而後在向樹中添加或者刪除RenderObject對象時會調用adoptChilddropChild函數,而這兩個函數都會調用markNeedsCompositingBitsUpdate函數,也就在markNeedsCompositingBitsUpdate函數內完成了將當前對象添加到集合中的操做。

//向樹中添加當前節點
  @override
  void adoptChild(RenderObject child) {
    setupParentData(child);
    markNeedsLayout();
    //將當前對象的_needsCompositingBitsUpdate值標爲true
    markNeedsCompositingBitsUpdate();
    markNeedsSemanticsUpdate();
    super.adoptChild(child);
  }
  //從樹中移除當前節點
  @override
  void dropChild(RenderObject child) {
    child._cleanRelayoutBoundary();
    child.parentData.detach();
    child.parentData = null;
    super.dropChild(child);
    markNeedsLayout();
    //將當前對象的_needsCompositingBitsUpdate值標爲true
    markNeedsCompositingBitsUpdate();
    markNeedsSemanticsUpdate();
  }
  //
  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;
      }
    }
    //將當前對象或者其父對象添加到_nodesNeedingCompositingBitsUpdate集合中
    if (owner != null)
      owner._nodesNeedingCompositingBitsUpdate.add(this);
  }
複製代碼

這樣就會在調用flushCompositingBits函數時,就會調用_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;
  }
複製代碼

五、繪製階段

通過前面的佈局及「髒」RenderObject對象的標記,如今就能夠在圖層上進行UI的繪製。經過調用flushPaint函數就能夠重繪已經標記的「髒」RenderObject對象及其子對象。

void flushPaint() {
    try {
      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) {
          //當前對象是否與layer進行關聯
          if (node._layer.attached) {
            //在圖層上繪製UI
            PaintingContext.repaintCompositedChild(node);
          } else {
            //跳過UI繪製,但當前節點爲「髒」的狀態不會改變
            node._skippedPaintingOnLayer();
          }
        }
      }
    } finally {}
  }
複製代碼

flushPaint函數中,每次遍歷「髒」RenderObject對象時,都會進行一次排序,避免重複繪製。而後在判斷當前對象是否與Layer進行關聯,若是沒有關聯,則沒法進行繪製,但不會清除「髒」標記。下面來看repaintCompositedChild函數的實現。

static void repaintCompositedChild(RenderObject child, { bool
    _repaintCompositedChild(
      child,
      debugAlsoPaintedParent: debugAlsoPaintedParent,
    );
  }

  static void _repaintCompositedChild(
    RenderObject child, {
    bool debugAlsoPaintedParent = false,
    PaintingContext childContext,
  }) {
    //拿到Layer對象
    OffsetLayer childLayer = child._layer;
    if (childLayer == null) {
      //建立新的Layer對象
      child._layer = childLayer = OffsetLayer();
    } else {
      //移除Layer對象的後繼節點
      childLayer.removeAllChildren();
    }
    //建立context對象
    childContext ??= PaintingContext(child._layer, child.paintBounds);
    //調用paint函數開始繪製
    child._paintWithContext(childContext, Offset.zero);
    childContext.stopRecordingIfNeeded();
  }
複製代碼

在該函數中主要是對Layer對象的處理,而後調用_paintWithContext函數,在_paintWithContext函數中就會調用paint這個函數,從而實現UI的繪製。至此,就完成了UI的繪製,下面再來看一個被忽略的對象——Layer

5.一、Layer

Layer是「圖層」意思,在Flutter中是最容易被忽略但又無比重要的一個類。它很是貼近底層,能夠很容易的看到調用Native方法。

Layer跟其餘三棵樹同樣,也是一棵樹,有「髒」狀態的標記、更新等操做。不一樣的是,Layer是一個雙鏈表結構,在每一個Layer對象中都會指向其前置節點與後置節點(葉子Layer的後置節點爲null)。

abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
  //返回父節點
  @override
  ContainerLayer get parent => super.parent;

  //當前節點狀態,爲true表示當前節點是「髒」數據。須要重繪
  bool _needsAddToScene = true;

  //將當前節點標記爲「髒」
  @protected
  @visibleForTesting
  void markNeedsAddToScene() {
    // Already marked. Short-circuit.
    if (_needsAddToScene) {
      return;
    }
    _needsAddToScene = true;
  }
  
  @protected
  bool get alwaysNeedsAddToScene => false;

  //這個是一個很是重要的東西,主要用於節點數據的緩存。存儲當前節點的渲染數據,若是當前節點不須要更新,就直接拿存儲的數據使用。
  @protected
  ui.EngineLayer get engineLayer => _engineLayer;
  
  //更改當前節點的數據
  @protected
  set engineLayer(ui.EngineLayer value) {
    _engineLayer = value;
      if (parent != null && !parent.alwaysNeedsAddToScene) {
        //將父節點標記須要更新的狀態
        parent.markNeedsAddToScene();
      }
    }
  }
  ui.EngineLayer _engineLayer;

  //更新當前節點狀態,若是_needsAddToScene爲true,則將當前節點標記爲「髒」
  @protected
  @visibleForTesting
  void updateSubtreeNeedsAddToScene() {
    _needsAddToScene = _needsAddToScene || alwaysNeedsAddToScene;
  }

  //指向後置節點
  Layer get nextSibling => _nextSibling;
  Layer _nextSibling;

  //指向前置節點
  Layer get previousSibling => _previousSibling;
  Layer _previousSibling;
  
  //將子節點從Layer樹中移除
  @override
  void dropChild(AbstractNode child) {
    if (!alwaysNeedsAddToScene) {
      markNeedsAddToScene();
    }
    super.dropChild(child);
  }
  //將當前節點添加到Layer樹中
  @override
  void adoptChild(AbstractNode child) {
    if (!alwaysNeedsAddToScene) {
      markNeedsAddToScene();
    }
    super.adoptChild(child);
  }

  //將當前節點從Layer樹中移除
  @mustCallSuper
  void remove() {
    parent?._removeChild(this);
  }

  
  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]);
  
  void _addToSceneWithRetainedRendering(ui.SceneBuilder builder) {
    //使用當前節點的緩存的數據
    if (!_needsAddToScene && _engineLayer != null) {
      builder.addRetained(_engineLayer);
      return;
    }
    addToScene(builder);
    //將當前節點標記爲「乾淨」的
    _needsAddToScene = false;
  }
}
複製代碼

5.二、Layer節點的添加

previousSiblingnextSibling分別是Layer的前置節點與後置節點,當向Layer樹中添加Layer節點時,也會將當前Layer設置爲父節點的後置節點,父節點設置爲當前節點的前置節點。這樣,就造成了一顆樹。

class ContainerLayer extends Layer {
  ...
  //將當前節點及其鏈表上的全部子節點都加入到Layer樹中
  @override
  void attach(Object owner) {
    super.attach(owner);
    Layer child = firstChild;
    while (child != null) {
      child.attach(owner);
      child = child.nextSibling;
    }
  }
  
  //將當前節點及其鏈表上的全部子節點都從Layer樹中移除
  @override
  void detach() {
    super.detach();
    Layer child = firstChild;
    while (child != null) {
      child.detach();
      child = child.nextSibling;
    }
  }
  //將child添加到鏈表中
  void append(Layer child) {
    adoptChild(child);
    child._previousSibling = lastChild;
    if (lastChild != null)
      lastChild._nextSibling = child;
    _lastChild = child;
    _firstChild ??= child;
  }
  ...
}
複製代碼

在上述的append函數中就將子節點添加到Layer樹並加入到雙鏈表中。在adoptChild函數中最終會調用attach函數,從而完成Layer樹的添加。

5.三、Layer的狀態更新

_needsAddToScene是對Layer狀態的標記,若是爲true,則表示當前Layer須要重寫進行繪製,不然表示當前Layer是「乾淨」的,不須要從新繪製,只須要拿Layer上次的數據與其餘Layer數據一塊兒交給GPU處理便可。從而達到節省資源的目的。

class ContainerLayer extends Layer {
  ...
  //更新Layer節點的狀態。
  @override
  void updateSubtreeNeedsAddToScene() {
    super.updateSubtreeNeedsAddToScene();
    Layer child = firstChild;
    while (child != null) {
      child.updateSubtreeNeedsAddToScene();
      _needsAddToScene = _needsAddToScene || child._needsAddToScene;
      child = child.nextSibling;
    }
  }
  ...
}
複製代碼

updateSubtreeNeedsAddToScene函數就是更新Layer的狀態,能夠發現,在更新當前Layer的狀態時,也會更新其全部子Layer的狀態。

關於Layer的更多內容能夠去閱讀Flutter Framework 源碼解析( 2 )—— 圖層詳解這篇文章。

六、其餘階段

6.一、compositing階段

該階段主要是將更新後的數據傳遞給GPU。這時候調用的是compositeFrame函數,該函數很簡單,就是調用了一個Native函數。

void compositeFrame() {
    Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);
    try {
      final ui.SceneBuilder builder = ui.SceneBuilder();
      final ui.Scene scene = layer.buildScene(builder);
      if (automaticSystemUiAdjustment)
        _updateSystemChrome();
      //更新後數據交給GPU處理
      _window.render(scene);
      scene.dispose();
    } finally {
      Timeline.finishSync();
    }
  }
複製代碼

6.二、semantics階段

在向GPU發送數據後,Flutter還會調用flushSemantics函數。該函數與系統的輔助功能相關,通常狀況下是不作任何處理。

6.三、finalization階段

在該階段,主要是將Element對象從樹中移除及處理添加在_postFrameCallbacks集合中的回調函數。因爲該回調函數是在繪製結束時調用,因此在該回調函數中,context已經建立成功。

七、總結

能夠發現,Flutter的UI繪製仍是蠻複雜的,涉及到的東西也比較多,如動畫的處理、輔助功能、異步任務等。但總體上仍是經過WidgetElementRenderObject這三棵樹來操做layer樹實現的UI的繪製。 熟悉了這四棵樹,也就會對Flutter的UI繪製有一個清晰的認識。

相關文章
相關標籤/搜索