flutter —— setState 是否一定會觸發節點重繪

  1. 調用 markNeedsBuild,標記element爲dirty 。
  2. 調用scheduleBuildFor,將當前element添加到一個全局的dirtyElements列表。
  3. 請求一個新的frame,隨後會繪製新的frame,onBuildScheduled->ensureVisualUpdate->scheduleFrame()

繪製過程:

WidgetsBinding.drawFrame:

void drawFrame() {
  buildOwner!.buildScope(renderViewElement!); //重新構建widget樹
  pipelineOwner.flushLayout(); // 更新佈局
  pipelineOwner.flushCompositingBits(); //更新合成信息
  pipelineOwner.flushPaint(); // 更新繪製
  if (sendFramesToEngine) {
    renderView.compositeFrame(); // 上屏,會將繪製出的bit數據發送給GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
    _firstFrameSent = true;
  }
}

① 重新構建widget樹:如果dirtyElements列表不爲空,則遍歷該列表,調用每一個element的 rebuild 方法重新構建新的widget(樹),由於新的widget(樹)使用新的狀態構建,所以可能導致widget佈局信息(佔用的空間和位置)發生變化,如果發生變化,則會調用其renderObject的 markNeedsLayout 方法,該方法會從當前節點向父級查找,直到找到一個relayoutBoundary的節點,然後會將它添加到一個全局的nodesNeedingLayout列表中;如果直到根節點也沒有找到relayoutBoundary,則將根節點添加到 nodesNeedingLayout 列表中。

② 更新佈局:遍歷nodesNeedingLayout數組,對每一個renderObject重新佈局(調用其layout方法),確定新的大小和偏移。layout方法中會調用 markNeedsPaint(),該方法和 markNeedsLayout 方法功能類似,也會從當前節點向父級查找,直到找到一個isRepaintBoundary屬性爲true的父節點,然後將它添加到一個全局的 nodesNeedingPaint 列表中;由於根節點(RenderView)的 isRepaintBoundary 爲 true,所以必會找到一個。查找過程結束後會調用buildOwner.requestVisualUpdate方法,該方法最終會調用scheduleFrame(),該方法中會先判斷是否已經請求過新的frame,如果沒有則請求一個新的frame。

③ 更新合成信息。

④ 更新繪製:遍歷nodesNeedingPaint列表,調用每一個節點的paint方法進行重繪,繪製過程會生成Layer。需要說明一下,flutter中繪製結果是保存在Layer中的,也就是說只要Layer不釋放,那麼繪製的結果就會被緩存,因此,Layer可以跨frame來緩存繪製結果,避免不必要的重繪開銷。Flutter框架繪製過程中,遇到isRepaintBoundary 爲 true 的節點時,纔會生成一個新的Layer。可見Layer和 renderObject 不是一一對應關係,父子節點可以共享,這個我們會在隨後的一個試驗中來驗證。當然,如果是自定義組件,我們可以在renderObject中手動添加任意多個 Layer,這通常用於只需一次繪製而隨後不會發生變化的繪製元素的緩存場景,這個隨後我們也會通過一個例子來演示。

⑤ 上屏:繪製完成後,我們得到的是一棵Layer樹,最後我們需要將Layer樹中的繪製信息在屏幕上顯示。我們知道Flutter是自實現的渲染引擎,因此,我們需要將繪製信息提交給Flutter engine,而renderView.compositeFrame 正是完成了這個使命。

問:setState 是否一定會觸發節點重繪嗎?

答:setState 會執行 markNeedsBuild,這會導致 widget 重建。重新構建widget樹後,如果檢查 widget 的佈局信息沒有發生變化,則不會導致子節點發生重繪。

問:setState 一定會觸發 drawFrame 嗎?

答:setState 只是標記 dirty,需不需要執行 drawFrame 根據 schedulerPhase 判斷。比如當前渲染線程已正在執行繪製工作,則本次不執行 drawFrame;此外 setState 執行的時候還會先判斷當前 dirty 值,如果爲 true 則會直接返回(如在State 組件的 build 方法中直接執行 setState)。

void ensureVisualUpdate() {
  switch (schedulerPhase) {
    case SchedulerPhase.idle:
    case SchedulerPhase.postFrameCallbacks:
      scheduleFrame(); // 請求新的frame
      return;
    case SchedulerPhase.transientCallbacks:
    case SchedulerPhase.midFrameMicrotasks:
    case SchedulerPhase.persistentCallbacks: // 注意這一行
      return;
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章