Flutter的布局和繪制詳解 ---布局和繪制

上一篇文章我們講了Flutter的三棵樹是如何創(chuàng)建并建立聯(lián)系的,有了這三棵樹,本篇文章將分析布局和繪制的流程。

1.布局與繪制入口

我們摘抄一段《Flutter實(shí)戰(zhàn)·第二版》的原文:

一次繪制過(guò)程,我們稱其為一幀(frame)。我們之前說(shuō)的 Flutter 可以實(shí)現(xiàn)60fps(Frame Per-Second)就是指一秒鐘最多可以觸發(fā) 60 次重繪,F(xiàn)PS 值越大,界面就越流暢。這里需要說(shuō)明的是 Flutter中 的 frame 概念并不等同于屏幕刷新幀(frame),因?yàn)镕lutter UI 框架的 frame 并不是每次屏幕刷新都會(huì)觸發(fā),這是因?yàn)?,如?UI 在一段時(shí)間不變,那么每次屏幕刷新都重新走一遍渲染流程是不必要的,因此,F(xiàn)lutter 在第一幀渲染結(jié)束后會(huì)采取一種主動(dòng)請(qǐng)求 frame 的方式來(lái)實(shí)現(xiàn)只有當(dāng)UI可能會(huì)改變時(shí)才會(huì)重新走渲染流程。

由此我們可知,F(xiàn)lutter其實(shí)并不是完全根據(jù)屏幕刷新頻率來(lái)發(fā)送重繪信號(hào)進(jìn)行繪制的。事實(shí)上,通常我們?cè)趯?shí)際中進(jìn)行繪制的主要有兩種場(chǎng)景:

  1. App啟動(dòng),即runApp()。
  2. 數(shù)據(jù)變化時(shí)發(fā)起重繪,比如:setState()。

1.1.runApp()

我們先回顧上一篇文章說(shuō)到的啟動(dòng)流程:程序入口runApp()的實(shí)現(xiàn):

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

在這個(gè)過(guò)程中創(chuàng)建了Flutter的三棵樹,并將三棵樹進(jìn)行綁定。最終調(diào)用scheduleWarmUpFrame()觸發(fā)首幀的繪制。scheduleWarmUpFrame()會(huì)執(zhí)行到handleDrawFrame()方法,核心實(shí)現(xiàn)是在RendererBindingdrawFrame()方法中:

  @protected
  void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    if (sendFramesToEngine) {
      renderView.compositeFrame(); // this sends the bits to the GPU
      pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
      _firstFrameSent = true;
    }
  }

即真正的每一幀繪制過(guò)程,這個(gè)過(guò)程我們之后分析。由此我們可知在啟動(dòng)App的時(shí)候,不發(fā)送Vsync信號(hào)即可觸發(fā)首幀的繪制。

1.2.setState()

另一個(gè)常用場(chǎng)景就是當(dāng)數(shù)據(jù)發(fā)生變化時(shí),我們調(diào)用setState()進(jìn)行更新重繪。我們看一下setState()的實(shí)現(xiàn):

  @protected
  void setState(VoidCallback fn) {
//...
    _element!.markNeedsBuild();
  }

核心實(shí)現(xiàn)就是_element!.markNeedsBuild()。

 void markNeedsBuild() {
    assert(_lifecycleState != _ElementLifecycle.defunct);
    if (_lifecycleState != _ElementLifecycle.active) {
      return;
    }
//...
    if (dirty) {
      return;
    }
    _dirty = true;
    owner!.scheduleBuildFor(this);
  }

Widget所對(duì)應(yīng)的Element標(biāo)記為dirty。這個(gè)owner就是我們上一篇文章中提到的在WidgetsBinding初始化時(shí)創(chuàng)建的BuildOwner對(duì)象,我們回顧一下它的創(chuàng)建:

@override
  void initInstances() {
    super.initInstances();
//...
    _buildOwner = BuildOwner();
    buildOwner!.onBuildScheduled = _handleBuildScheduled;
//...
  }

buildOwner持有個(gè)onBuildScheduledcallBack,它的實(shí)現(xiàn)是:

void _handleBuildScheduled() {
//...
    ensureVisualUpdate();
  }

其中ensureVisualUpdate()SchedulerBinding的方法,其實(shí)現(xiàn)如下:

void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
  }
  void scheduleFrame() {
    if (_hasScheduledFrame || !framesEnabled) {
      return;
    }
    assert(() {
      if (debugPrintScheduleFrameStacks) {
        debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
      }
      return true;
    }());
    ensureFrameCallbacksRegistered();
    platformDispatcher.scheduleFrame();
    _hasScheduledFrame = true;
  }
void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';

實(shí)際上就是請(qǐng)求新的frame。那么onBuildScheduled是什么時(shí)候被調(diào)用的呢?回顧完BuildOwner我們?cè)倩氐?code>setState(),看看owner!.scheduleBuildFor(this)的實(shí)現(xiàn):

void scheduleBuildFor(Element element) {
//...
    if (element._inDirtyList) {
//...
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled!();
    }
    _dirtyElements.add(element);
    element._inDirtyList = true;
//...
  }

我們看到了一個(gè)熟悉的方法執(zhí)行:BuildOwneronBuildScheduled被觸發(fā)了。也就是請(qǐng)求了下一幀的frame等待被重繪,再把Element添加到dirty element列表中等待下一幀的rebuild。
setState()的流程也分析完了,我們總結(jié)一下:在啟動(dòng)App的時(shí)候,不會(huì)發(fā)送Vsync信號(hào),直接進(jìn)行首幀的繪制。而在數(shù)據(jù)發(fā)生變化需要重繪,比如 setState()被觸發(fā)時(shí),會(huì)將Widget對(duì)應(yīng)的Element標(biāo)記為dirty,然后發(fā)送Vsync信號(hào)等待被重繪。
在分析具體的繪制流程之前,我們分析一下發(fā)送Vsync信號(hào)后是如何觸發(fā)重繪流程的。

2.SchedulerBinding

我們回到WidgetsFlutterBinding,上一篇文章介紹過(guò),WidgetsFlutterBinding混合了各種Binding。這些Binding基本都是監(jiān)聽并處理Window對(duì)象的一些事件,然后將這些事件按照Framework的模型包裝、抽象然后分發(fā)。其中SchedulerBinding監(jiān)聽刷新事件,綁定Framework繪制調(diào)度。
我們回到上一章節(jié)提到的scheduleFrame()的實(shí)現(xiàn),它是SchedulerBinding的方法:

  void scheduleFrame() {
    if (_hasScheduledFrame || !framesEnabled) {
      return;
    }
    assert(() {
      if (debugPrintScheduleFrameStacks) {
        debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
      }
      return true;
    }());
    ensureFrameCallbacksRegistered();
    platformDispatcher.scheduleFrame();
    _hasScheduledFrame = true;
  }

在執(zhí)行platformDispatcher.scheduleFrame()發(fā)起重繪之前調(diào)用了ensureFrameCallbacksRegistered()

  @protected
  void ensureFrameCallbacksRegistered() {
    platformDispatcher.onBeginFrame ??= _handleBeginFrame;
    platformDispatcher.onDrawFrame ??= _handleDrawFrame;
  }

platformDispatcher注冊(cè)了onBeginFrameonDrawFrame回調(diào)。我們追蹤一下onBeginFrameonDrawFrame的調(diào)用時(shí)機(jī):

FrameCallback? get onBeginFrame => _onBeginFrame;
  set onBeginFrame(FrameCallback? callback) {
    _onBeginFrame = callback;
    _onBeginFrameZone = Zone.current;
  }
VoidCallback? get onDrawFrame => _onDrawFrame;
  set onDrawFrame(VoidCallback? callback) {
    _onDrawFrame = callback;
    _onDrawFrameZone = Zone.current;
  }

get方法是在SingletonFlutterWindow中被執(zhí)行的:

  FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame;
  VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame;

SingletonFlutterWindowonBeginFrameonDrawFrame就是當(dāng)Engine層收到Vsync信號(hào)時(shí)被回調(diào)的,也就是說(shuō)發(fā)送了重繪信號(hào)后,將會(huì)在此時(shí)被執(zhí)行。我們回到SchedulerBindingensureFrameCallbacksRegistered()方法:

  @protected
  void ensureFrameCallbacksRegistered() {
    platformDispatcher.onBeginFrame ??= _handleBeginFrame;
    platformDispatcher.onDrawFrame ??= _handleDrawFrame;
  }

追蹤_handleBeginFrame_handleDrawFrame的實(shí)現(xiàn):
_handleBeginFrame:

  void _handleBeginFrame(Duration rawTimeStamp) {
    if (_warmUpFrame) {
//...
      _rescheduleAfterWarmUpFrame = true;
      return;
    }
    handleBeginFrame(rawTimeStamp);
  }
void handleBeginFrame(Duration? rawTimeStamp) {
    _frameTimelineTask?.start('Frame');
    _firstRawTimeStampInEpoch ??= rawTimeStamp;
    _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
    if (rawTimeStamp != null) {
      _lastRawTimeStamp = rawTimeStamp;
    }
//...
    _hasScheduledFrame = false;
    try {
      // TRANSIENT FRAME CALLBACKS
      _frameTimelineTask?.start('Animate');
      _schedulerPhase = SchedulerPhase.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 {
      _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
    }
  }

首先記錄了一下繪制初始的時(shí)間戳,然后遍歷_transientCallbacks逐一進(jìn)行回調(diào)。_transientCallbacks是執(zhí)行“臨時(shí)”回調(diào)任務(wù),”臨時(shí)“回調(diào)任務(wù)只能被執(zhí)行一次,執(zhí)行后會(huì)被移出”臨時(shí)“任務(wù)隊(duì)列。典型的代表就是動(dòng)畫回調(diào)會(huì)在該階段執(zhí)行。
_handleDrawFrame

void _handleDrawFrame() {
//...
    handleDrawFrame();
  }
void handleDrawFrame() {
    assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
    _frameTimelineTask?.finish(); // end the "Animate" phase
    try {
      // PERSISTENT FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (final FrameCallback callback in _persistentCallbacks) {
        _invokeFrameCallback(callback, _currentFrameTimeStamp!);
      }

      // POST-FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
      final List<FrameCallback> localPostFrameCallbacks =
          List<FrameCallback>.of(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      for (final FrameCallback callback in localPostFrameCallbacks) {
        _invokeFrameCallback(callback, _currentFrameTimeStamp!);
      }
    } finally {
//...
    }
  }

它遍歷了persistentCallbackpostFrameCallbacks,并逐一執(zhí)行。其中persistentCallback執(zhí)行一些持久的任務(wù)(每一個(gè)frame都要執(zhí)行的任務(wù)),比如渲染管線(構(gòu)建、布局、繪制)就是在該任務(wù)隊(duì)列中執(zhí)行的。postFrameCallbacks在當(dāng)前 frame 在結(jié)束之前將會(huì)執(zhí)行 postFrameCallbacks,通常進(jìn)行一些清理工作和請(qǐng)求新的frame。由此可知persistentCallback就是具體布局繪制的地方,那么persistentCallback是在哪里注冊(cè)的呢?我們回顧一下上一篇文章的RendererBinding的初始化:

@override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    platformDispatcher
      ..onMetricsChanged = handleMetricsChanged
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
      ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
      ..onSemanticsAction = _handleSemanticsAction;
    initRenderView();
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    initMouseTracker();
    if (kIsWeb) {
      addPostFrameCallback(_handleWebFirstFrame);
    }
  }

initInstances()時(shí)會(huì)調(diào)用addPersistentFrameCallback(_handlePersistentFrameCallback)方法進(jìn)行注冊(cè),而_handlePersistentFrameCallback的實(shí)現(xiàn)如下:

  void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
    _scheduleMouseTrackerUpdate();
  }

執(zhí)行了drawFrame()

  @protected
  void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    if (sendFramesToEngine) {
      renderView.compositeFrame(); // this sends the bits to the GPU
      pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
      _firstFrameSent = true;
    }
  }

好到此為止當(dāng)數(shù)據(jù)發(fā)生變化,比如執(zhí)行setState()時(shí)發(fā)送Vsync信號(hào)請(qǐng)求重繪也調(diào)到了drawFrame()方法。真正的布局和繪制是由pipelineOwner這個(gè)渲染管道進(jìn)行處理的。接下來(lái)的章節(jié)才要開始我們的正題:Flutter的布局和繪制流程。

3.flushLayout()

flushLayout確定每一個(gè)組件的布局信息(大小和位置)。我們先來(lái)看它的實(shí)現(xiàn):

void flushLayout() {
    if (!kReleaseMode) {
      Map<String, String>? debugTimelineArguments;
//...
    try {
      while (_nodesNeedingLayout.isNotEmpty) {
        assert(!_shouldMergeDirtyNodes);
        final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
        _nodesNeedingLayout = <RenderObject>[];
        dirtyNodes.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
        for (int i = 0; i < dirtyNodes.length; i++) {
          if (_shouldMergeDirtyNodes) {
            _shouldMergeDirtyNodes = false;
            if (_nodesNeedingLayout.isNotEmpty) {
              _nodesNeedingLayout.addAll(dirtyNodes.getRange(i, dirtyNodes.length));
              break;
            }
          }
          final RenderObject node = dirtyNodes[I];
          if (node._needsLayout && node.owner == this) {
            node._layoutWithoutResize();
          }
        }
        _shouldMergeDirtyNodes = false;
      }
    } finally {
//...
    }
  }

_nodesNeedingLayoutRenderObject列表,表示需要被重新布局的節(jié)點(diǎn)。遍歷_nodesNeedingLayout,并執(zhí)行node._layoutWithoutResize()方法。
那么這個(gè)_nodesNeedingLayout是如何生成的呢?
通過(guò)跟蹤代碼,我們發(fā)現(xiàn)時(shí)RenderObject調(diào)用其markNeedsLayout()方法為_nodesNeedingLayout添加數(shù)據(jù)的:

void markNeedsLayout() {
//...
    if (_relayoutBoundary == null) {
      _needsLayout = true;
      if (parent != null) {
        markParentNeedsLayout();
      }
      return;
    }
    if (_relayoutBoundary != this) {
      markParentNeedsLayout();
    } else {
      _needsLayout = true;
      if (owner != null) {
//...
        owner!._nodesNeedingLayout.add(this);
        owner!.requestVisualUpdate();
      }
    }
  }

這段代碼的目的是通過(guò)找到_relayoutBoundary來(lái)確定需要重新被布局的節(jié)點(diǎn)。如果自己已經(jīng)是_relayoutBoundary,那么執(zhí)行owner!._nodesNeedingLayout.add(this)將自己添加到_nodesNeedingLayout中;否則只要有parent,那么就調(diào)用markParentNeedsLayout()到父節(jié)點(diǎn)去尋找_relayoutBoundary:

  @protected
  void markParentNeedsLayout() {
    assert(_debugCanPerformMutations);
    _needsLayout = true;
    assert(this.parent != null);
    final RenderObject parent = this.parent! as RenderObject;
    if (!_doingThisLayoutWithCallback) {
      parent.markNeedsLayout();
    } else {
      assert(parent._debugDoingThisLayout);
    }
    assert(parent == this.parent);
  }

這是個(gè)遞歸的過(guò)程,parent會(huì)調(diào)用parent.markNeedsLayout(),目的就是找到_relayoutBoundary添加到_nodesNeedingLayout。
那么這個(gè)_relayoutBoundary是如何確定的呢?之后再進(jìn)行說(shuō)明。
另外markNeedsLayout()還有什么調(diào)用時(shí)機(jī)呢?通過(guò)追蹤,在RenderObjectattach()方法會(huì)執(zhí)行markNeedsLayout()

  @override
  void attach(PipelineOwner owner) {
    assert(!_debugDisposed);
    super.attach(owner);
    if (_needsLayout && _relayoutBoundary != null) {
      _needsLayout = false;
      markNeedsLayout();
    }
    if (_needsCompositingBitsUpdate) {
      _needsCompositingBitsUpdate = false;
      markNeedsCompositingBitsUpdate();
    }
    if (_needsPaint && _layerHandle.layer != null) {
      _needsPaint = false;
      markNeedsPaint();
    }
    if (_needsSemanticsUpdate && _semanticsConfiguration.isSemanticBoundary) {
      _needsSemanticsUpdate = false;
      markNeedsSemanticsUpdate();
    }
  }

我們回到flushLayout(),看看node._layoutWithoutResize()的實(shí)現(xiàn):

void _layoutWithoutResize() {
    assert(_relayoutBoundary == this);
    RenderObject? debugPreviousActiveLayout;
//...
    try {
      performLayout();
      markNeedsSemanticsUpdate();
    } catch (e, stack) {
      _debugReportException('performLayout', e, stack);
    }
//...
    _needsLayout = false;
    markNeedsPaint();
  }

主要執(zhí)行了兩個(gè)方法:performLayout()markNeedsPaint()。在布局中最重要的實(shí)現(xiàn)就是performLayout()的實(shí)現(xiàn)。我們先來(lái)看performLayout(),它是RenderObject的一個(gè)抽象方法,需要子類來(lái)實(shí)現(xiàn)。
performLayout()需要做這么幾件事:

  1. 父節(jié)點(diǎn)向子節(jié)點(diǎn)傳遞約束(constraints)信息,限制子節(jié)點(diǎn)的最大和最小寬高。
  2. 子節(jié)點(diǎn)根據(jù)約束信息確定自己的大?。╯ize)。
  3. 父節(jié)點(diǎn)根據(jù)特定布局規(guī)則(不同布局組件會(huì)有不同的布局算法)確定每一個(gè)子節(jié)點(diǎn)在父節(jié)點(diǎn)布局空間中的位置,用偏移 offset 表示。
  4. 遞歸整個(gè)過(guò)程,確定出每一個(gè)節(jié)點(diǎn)的大小和位置。

我們看個(gè)例子,比如說(shuō)Align這個(gè)控件。Align繼承SingleChildRenderObjectWidget,它開放了createRenderObject()方法由子類實(shí)現(xiàn),返回一個(gè)RenderObject對(duì)象。我們看看Align的實(shí)現(xiàn):

  @override
  RenderPositionedBox createRenderObject(BuildContext context) {
    return RenderPositionedBox(
      alignment: alignment,
      widthFactor: widthFactor,
      heightFactor: heightFactor,
      textDirection: Directionality.maybeOf(context),
    );
  }

RenderPositionedBoxperformLayout()實(shí)現(xiàn)如下:

  @override
  void performLayout() {
    final BoxConstraints constraints = this.constraints;
    final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
    final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;

    if (child != null) {
      child!.layout(constraints.loosen(), parentUsesSize: true);
      size = constraints.constrain(Size(
        shrinkWrapWidth ? child!.size.width * (_widthFactor ?? 1.0) : double.infinity,
        shrinkWrapHeight ? child!.size.height * (_heightFactor ?? 1.0) : double.infinity,
      ));
      alignChild();
    } else {
//...
    }
  }
  @protected
  void alignChild() {
    _resolve();
//...
    final BoxParentData childParentData = child!.parentData! as BoxParentData;
    childParentData.offset = _resolvedAlignment!.alongOffset(size - child!.size as Offset);
  }

只考慮Alignchild的情況:

  1. constraints即約束條件,限制子節(jié)點(diǎn)的最大和最小寬高。
  2. 先對(duì)child進(jìn)行layout(),并獲取childsize。
  3. 根據(jù)child的大小確定自身的大小size。
  4. 執(zhí)行alignChild()算出childAlign組件中的偏移,然后將這個(gè)偏移保存在childparentData中,parentData在繪制階段會(huì)用到。

我們分析一下對(duì)child進(jìn)行layout(),layout()的實(shí)現(xiàn):

void layout(Constraints constraints, { bool parentUsesSize = false }) {
    if (!kReleaseMode && debugProfileLayoutsEnabled) {
      Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
//...
    final bool isRelayoutBoundary = !parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject;
    final RenderObject relayoutBoundary = isRelayoutBoundary ? this : (parent! as RenderObject)._relayoutBoundary!;
//...

    if (!_needsLayout && constraints == _constraints) {
//...

      if (relayoutBoundary != _relayoutBoundary) {
        _relayoutBoundary = relayoutBoundary;
        visitChildren(_propagateRelayoutBoundaryToChild);
      }

      if (!kReleaseMode && debugProfileLayoutsEnabled)
        Timeline.finishSync();
      return;
    }
    _constraints = constraints;
    if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {

      visitChildren(_cleanChildRelayoutBoundary);
    }
    _relayoutBoundary = relayoutBoundary;
//...
    if (sizedByParent) {
//...
      try {
        performResize();
//...
      } catch (e, stack) {
        _debugReportException('performResize', e, stack);
      }
//...
    }
    RenderObject? debugPreviousActiveLayout;
//...
    try {
      performLayout();
      markNeedsSemanticsUpdate();
//...
    } catch (e, stack) {
      _debugReportException('performLayout', e, stack);
    }
//...
    _needsLayout = false;
    markNeedsPaint();

    if (!kReleaseMode && debugProfileLayoutsEnabled)
      Timeline.finishSync();
  }

這段代碼非常核心,首先就確定了之前我們遺留的relayoutBoundary的疑問:

final bool isRelayoutBoundary = !parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject;
final RenderObject relayoutBoundary = isRelayoutBoundary ? this : (parent! as RenderObject)._relayoutBoundary!;

relayoutBoundary確定的原則是“組件自身的大小變化不會(huì)影響父組件”,如果一個(gè)組件滿足以下四種情況之一,則它便是relayoutBoundary

  1. parentUsesSize,即當(dāng)前組件父組件的大小不依賴當(dāng)前組件大小時(shí)。
  2. sizedByParent,組件的大小只取決于父組件傳遞的約束,而不會(huì)依賴子組件的大小。
  3. constraints.isTight,父組件傳遞給自身的約束是一個(gè)嚴(yán)格約束。這種情況下即使自身的大小依賴后代元素,但也不會(huì)影響父組件。
  4. parent is! RenderObject,說(shuō)明當(dāng)前組件是根組件,即RenderView,它沒有parent。

如果當(dāng)前組件不是relayoutBoundary,那么就把父組件的_relayoutBoundary賦值給自己的relayoutBoundary。
之后如果_needsLayoutfalse,且constraints約束條件沒有變化,則無(wú)需重新布局直接return
relayoutBoundary賦值給自己的成員變量_relayoutBoundary。
如果是sizedByParent的,則執(zhí)行performResize()方法。performResize()是個(gè)抽象方法,需要子類進(jìn)行實(shí)現(xiàn),我們?cè)僬欢巍禙lutter實(shí)戰(zhàn)·第二版》的原文:

performLayout中確定當(dāng)前組件的大小時(shí)通常會(huì)依賴子組件的大小,如果sizedByParenttrue,則當(dāng)前組件的大小就不依賴子組件大小了,為了邏輯清晰,F(xiàn)lutter 框架中約定,當(dāng)sizedByParenttrue時(shí),確定當(dāng)前組件大小的邏輯應(yīng)抽離到 performResize()中,這種情況下performLayout主要的任務(wù)便只有兩個(gè):對(duì)子組件進(jìn)行布局和確定子組件在當(dāng)前組件中的布局起始位置偏移。

回到layout()方法,執(zhí)行performLayout(),performLayout()又回調(diào)用childlayout()方法形成遞歸。之后將_needsLayout設(shè)置為false,然后調(diào)用markNeedsPaint()請(qǐng)求重繪:

void markNeedsPaint() {
//...
    if (_needsPaint) {
      return;
    }
    _needsPaint = true;
    // If this was not previously a repaint boundary it will not have
    // a layer we can paint from.
    if (isRepaintBoundary && _wasRepaintBoundary) {
//...
      if (owner != null) {
        owner!._nodesNeedingPaint.add(this);
        owner!.requestVisualUpdate();
      }
    } else if (parent is RenderObject) {
      final RenderObject parent = this.parent! as RenderObject;
      parent.markNeedsPaint();
      assert(parent == this.parent);
    } else {
//...
      if (owner != null) {
        owner!.requestVisualUpdate();
      }
    }
  }

大概的邏輯是如果自己是isRepaintBoundary,那么將自己添加到_nodesNeedingPaint列表中,_nodesNeedingPaint將在繪制的時(shí)候使用;否則如果有父的話調(diào)用父的markNeedsPaint()形成遞歸。
到此為止,flushLayout()到實(shí)現(xiàn)就分析完畢了。我們繼續(xù)分析flushCompositingBits()。

4.flushCompositingBits()

4.1.Layer

Layer需要拿出來(lái)單獨(dú)寫一篇文章去講,在此只是介紹一下,以便更好的理解繪制過(guò)程。
Flutter的繪制會(huì)將RenderObject樹轉(zhuǎn)成Layer樹,Layer的組成由上一章節(jié)提到的RenderObject中的isRepaintBoundary來(lái)決定。
Layer有兩種類型。

  1. 容器類Layer:最常用的是OffsetLayer,通常情況下一個(gè)repaintBoundary就對(duì)應(yīng)一個(gè)OffsetLayer。比方說(shuō)每一個(gè)路由都封裝了一個(gè)repaintBoundary,那么每一個(gè)路由就在一個(gè)單獨(dú)的Layer里。另外容器類 Layer可以對(duì)其子Layer整體做一些變換效果,比如剪裁效果(ClipRectLayerClipRRectLayer、ClipPathLayer)、過(guò)濾效果(ColorFilterLayer、ImageFilterLayer)、矩陣變換(TransformLayer)、透明變換(OpacityLayer)等。
  2. 繪制類Layer:最常用的是PictureLayer,比方說(shuō)我們常用的TextImage組件等就在PictureLayer里。另外還有TextureLayerPlatformViewLayer主要用于native相關(guān)的繪制。

Layer的作用一個(gè)是為了在不同的frame下復(fù)用繪制的產(chǎn)物,另外就是確定繪制邊界縮小繪制的區(qū)域。
關(guān)于Layer在此就不再做展開了,有空的時(shí)候會(huì)再寫一篇認(rèn)真分析一下Layer。我們回到flushCompositingBits()繼續(xù)分析。

4.2.flushCompositingBits()

  void flushCompositingBits() {
    if (!kReleaseMode) {
      Timeline.startSync('UPDATING COMPOSITING BITS');
    }
    _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
    for (final RenderObject node in _nodesNeedingCompositingBitsUpdate) {
      if (node._needsCompositingBitsUpdate && node.owner == this) {
        node._updateCompositingBits();
      }
    }
    _nodesNeedingCompositingBitsUpdate.clear();
    if (!kReleaseMode) {
      Timeline.finishSync();
    }
  }

遍歷_nodesNeedingCompositingBitsUpdate列表,并執(zhí)行_updateCompositingBits()方法。它的作用是標(biāo)記需要被合成的Layer所對(duì)應(yīng)的對(duì)象。

  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 a node was previously a repaint boundary, but no longer is one, then
    // regardless of its compositing state we need to find a new parent to
    // paint from. To do this, we mark it clean again so that the traversal
    // in markNeedsPaint is not short-circuited. It is removed from _nodesNeedingPaint
    // so that we do not attempt to paint from it after locating a parent.
    if (!isRepaintBoundary && _wasRepaintBoundary) {
      _needsPaint = false;
      _needsCompositedLayerUpdate = false;
      owner?._nodesNeedingPaint.remove(this);
      _needsCompositingBitsUpdate = false;
      markNeedsPaint();
    } else if (oldNeedsCompositing != _needsCompositing) {
      _needsCompositingBitsUpdate = false;
      markNeedsPaint();
    } else {
      _needsCompositingBitsUpdate = false;
    }
  }

遞歸遍歷子樹確定如果每一個(gè)節(jié)點(diǎn)的_needsCompositing值,在繪制時(shí)只需要判斷一下當(dāng)前的needsCompositing 就能知道子樹是否存在待合成的layer了。那么什么情況下Layer要被合成呢?我們回過(guò)頭看一下_nodesNeedingCompositingBitsUpdate是怎么來(lái)的。

void markNeedsCompositingBitsUpdate() {
    assert(!_debugDisposed);
    if (_needsCompositingBitsUpdate) {
      return;
    }
    _needsCompositingBitsUpdate = true;
    if (parent is RenderObject) {
      final RenderObject parent = this.parent! as RenderObject;
      if (parent._needsCompositingBitsUpdate) {
        return;
      }

      if ((!_wasRepaintBoundary || !isRepaintBoundary) && !parent.isRepaintBoundary) {
        parent.markNeedsCompositingBitsUpdate();
        return;
      }
    }
    // parent is fine (or there isn't one), but we are dirty
    if (owner != null) {
      owner!._nodesNeedingCompositingBitsUpdate.add(this);
    }
  }

調(diào)用markNeedsCompositingBitsUpdate()的主要場(chǎng)景是當(dāng)子節(jié)點(diǎn)會(huì)向Layer樹中添加新的繪制類Layer時(shí),則父級(jí)的變換類組件中就需要合成Layer。這個(gè)怎么理解呢?我們還是摘抄一個(gè)《Flutter實(shí)戰(zhàn)·第二版》的實(shí)例:

@override
Widget build(BuildContext context) {
  return Center(
    child: CustomRotatedBox(
      child: RepaintBoundary( // 添加一個(gè) RepaintBoundary
        child: Text(
          "A",
          textScaleFactor: 5,
        ),
      ),
    ),
  );
}

其中CustomRotatedBox這個(gè)控件的作用是將child旋轉(zhuǎn)90度。它的Layer結(jié)構(gòu)如下圖所示:

截屏2023-01-13 14.58.48.png

執(zhí)行上面的代碼,會(huì)發(fā)現(xiàn)Text("A")并沒有旋轉(zhuǎn)90度,原因是CustomRotatedBox中進(jìn)行旋轉(zhuǎn)變換的canvas對(duì)應(yīng)的是PictureLayer1,而 Text("A") 的繪制是使用的PictureLayer2對(duì)應(yīng)的canvas,他們屬于不同的 Layer??梢园l(fā)現(xiàn)父子的PictureLayer "分離了",所以CustomRotatedBox也就不會(huì)對(duì)Text("A")起作用。解決這個(gè)問題的方法是:

  1. 創(chuàng)建一個(gè)TransformLayer(記為TransformLayer1) 添加到Layer樹中,接著創(chuàng)建一個(gè)新的PaintingContextTransformLayer1綁定。
  2. 子節(jié)點(diǎn)通過(guò)這個(gè)新的PaintingContext去繪制。

如下圖所示:

截屏2023-01-13 15.06.52.png

這其實(shí)就是一個(gè)重新Layer合成的過(guò)程:創(chuàng)建一個(gè)新的ContainerLayer,然后將該ContainerLayer傳遞給子節(jié)點(diǎn),這樣后代節(jié)點(diǎn)的Layer必然屬于ContainerLayer,那么給這個(gè)ContainerLayer做變換就會(huì)對(duì)其全部的子孫節(jié)點(diǎn)生效。
回到我們之前的代碼,所以在flushCompositingBits()過(guò)程中其實(shí)就是給這些TransformLayer去打_needsCompositing標(biāo)記的,為之后的Layer合成做準(zhǔn)備。
到此為止,flushCompositingBits()到實(shí)現(xiàn)就分析完畢了。我們繼續(xù)分析flushPaint()。

5.flushPaint()

代碼如下:

void flushPaint() {
    if (!kReleaseMode) {
      Map<String, String>? debugTimelineArguments;
//...
    }
    try {
//...
      final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
      _nodesNeedingPaint = <RenderObject>[];

      // Sort the dirty nodes in reverse order (deepest first).
      for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
        assert(node._layerHandle.layer != null);
        if ((node._needsPaint || node._needsCompositedLayerUpdate) && node.owner == this) {
          if (node._layerHandle.layer!.attached) {
            assert(node.isRepaintBoundary);
            if (node._needsPaint) {
              PaintingContext.repaintCompositedChild(node);
            } else {
              PaintingContext.updateLayerProperties(node);
            }
          } else {
            node._skippedPaintingOnLayer();
          }
        }
      }
      assert(_nodesNeedingPaint.isEmpty);
    } finally {
//...
    }
  }

遍歷_nodesNeedingPaint列表,逐一執(zhí)行PaintingContext.repaintCompositedChild(node)方法:

  static void repaintCompositedChild(RenderObject child, { bool debugAlsoPaintedParent = false }) {
    assert(child._needsPaint);
    _repaintCompositedChild(
      child,
      debugAlsoPaintedParent: debugAlsoPaintedParent,
    );
  }
  static void _repaintCompositedChild(
    RenderObject child, {
    bool debugAlsoPaintedParent = false,
    PaintingContext? childContext,
  }) {
//...
    OffsetLayer? childLayer = child._layerHandle.layer as OffsetLayer?;
    if (childLayer == null) {
//...
      final OffsetLayer layer = child.updateCompositedLayer(oldLayer: null);
      child._layerHandle.layer = childLayer = layer;
    } else {
      assert(debugAlsoPaintedParent || childLayer.attached);
      Offset? debugOldOffset;
//...
      childLayer.removeAllChildren();
      final OffsetLayer updatedLayer = child.updateCompositedLayer(oldLayer: childLayer);
//...
    }
    child._needsCompositedLayerUpdate = false;
//...
    childContext ??= PaintingContext(childLayer, child.paintBounds);
    child._paintWithContext(childContext, Offset.zero);
//...
    childContext.stopRecordingIfNeeded();
  }

由于被重繪的部分一定是repaintBoundary,所以會(huì)持有一個(gè)OffsetLayer,再創(chuàng)建一個(gè)PaintingContext對(duì)象,之后調(diào)用child._paintWithContext(childContext, Offset.zero)進(jìn)行處理:

void _paintWithContext(PaintingContext context, Offset offset) {
//...
    if (_needsLayout) {
      return;
    }
    if (!kReleaseMode && debugProfilePaintsEnabled) {
      Map<String, String>? debugTimelineArguments;
//...
    RenderObject? debugLastActivePaint;
//...
    _needsPaint = false;
    _needsCompositedLayerUpdate = false;
    _wasRepaintBoundary = isRepaintBoundary;
    try {
      paint(context, offset);
//...
    } catch (e, stack) {
      _debugReportException('paint', e, stack);
    }
//...
  }

重要的paint()來(lái)了,paint()是個(gè)抽象方法,由子類進(jìn)行實(shí)現(xiàn)。paint()的實(shí)現(xiàn)思路通常是這樣的:如果是容器組件,要繪制自己和child,如果不是容器類組件,則繪制自己(比如Image)。自身的繪制就是在context.canvas上進(jìn)行繪制。

void paint(PaintingContext context, Offset offset) {
  // ...自身的繪制
  if(hasChild){ //如果該組件是容器組件,繪制子節(jié)點(diǎn)。
    context.paintChild(child, offset)
  }
  //...自身的繪制
}

我們看看context.paintChild(child, offset)的實(shí)現(xiàn):

  void paintChild(RenderObject child, Offset offset) {
//...

    if (child.isRepaintBoundary) {
      stopRecordingIfNeeded();
      _compositeChild(child, offset);
    } else if (child._wasRepaintBoundary) {
      assert(child._layerHandle.layer is OffsetLayer);
      child._layerHandle.layer = null;
      child._paintWithContext(this, offset);
    } else {
      child._paintWithContext(this, offset);
    }
  }

先判斷child是不是repaintBoundary,如果是且需要被合成的話,那么先將childLayer進(jìn)行合成,否則的話,遞歸執(zhí)行child._paintWithContext(this, offset),進(jìn)行繪制。
flushPaint()就梳理完成了,還差最后一個(gè)方法:renderView.compositeFrame()。

5.compositeFrame()

先看代碼:

void compositeFrame() {
    if (!kReleaseMode) {
      Timeline.startSync('COMPOSITING');
    }
    try {
      final ui.SceneBuilder builder = ui.SceneBuilder();
      final ui.Scene scene = layer!.buildScene(builder);
      if (automaticSystemUiAdjustment) {
        _updateSystemChrome();
      }
      _window.render(scene);
      scene.dispose();
//...
    } finally {
      if (!kReleaseMode) {
        Timeline.finishSync();
      }
    }
  }

它的邏輯是:先通過(guò)Layer構(gòu)建Scene,最后再通過(guò)window.render API 來(lái)渲染。這個(gè)過(guò)程我們就不分析了,總而言之就是將Layer樹中每一個(gè)Layer傳給Skia進(jìn)行繪制。

6.總結(jié)

通過(guò)《Flutter的布局和繪制詳解 ---三棵樹》和《Flutter的布局和繪制詳解 ---布局和繪制》這兩篇文章的梳理,我們知道了Flutter的頁(yè)面是如何構(gòu)建起來(lái),并進(jìn)行布局和繪制的,也了解了是通過(guò)什么方式節(jié)約資源的。希望能對(duì)大家有所幫助。

7.參考資料

https://book.flutterchina.club/chapter14/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容