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;
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章