上一篇文章我們講了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)景:
- App啟動(dòng),即
runApp()。 - 數(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)是在RendererBinding的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;
}
}
即真正的每一幀繪制過(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è)onBuildScheduled的callBack,它的實(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í)行:BuildOwner的onBuildScheduled被觸發(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è)了onBeginFrame和onDrawFrame回調(diào)。我們追蹤一下onBeginFrame和onDrawFrame的調(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;
SingletonFlutterWindow中onBeginFrame和onDrawFrame就是當(dāng)Engine層收到Vsync信號(hào)時(shí)被回調(diào)的,也就是說(shuō)發(fā)送了重繪信號(hào)后,將會(huì)在此時(shí)被執(zhí)行。我們回到SchedulerBinding的ensureFrameCallbacksRegistered()方法:
@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 {
//...
}
}
它遍歷了persistentCallback和postFrameCallbacks,并逐一執(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 {
//...
}
}
_nodesNeedingLayout是RenderObject列表,表示需要被重新布局的節(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ò)追蹤,在RenderObject的attach()方法會(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()需要做這么幾件事:
- 父節(jié)點(diǎn)向子節(jié)點(diǎn)傳遞約束(constraints)信息,限制子節(jié)點(diǎn)的最大和最小寬高。
- 子節(jié)點(diǎn)根據(jù)約束信息確定自己的大?。╯ize)。
- 父節(jié)點(diǎn)根據(jù)特定布局規(guī)則(不同布局組件會(huì)有不同的布局算法)確定每一個(gè)子節(jié)點(diǎn)在父節(jié)點(diǎn)布局空間中的位置,用偏移 offset 表示。
- 遞歸整個(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),
);
}
RenderPositionedBox的performLayout()實(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);
}
只考慮Align有child的情況:
-
constraints即約束條件,限制子節(jié)點(diǎn)的最大和最小寬高。 - 先對(duì)
child進(jìn)行layout(),并獲取child的size。 - 根據(jù)
child的大小確定自身的大小size。 - 執(zhí)行
alignChild()算出child在Align組件中的偏移,然后將這個(gè)偏移保存在child的parentData中,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:
- 非
parentUsesSize,即當(dāng)前組件父組件的大小不依賴當(dāng)前組件大小時(shí)。 -
sizedByParent,組件的大小只取決于父組件傳遞的約束,而不會(huì)依賴子組件的大小。 -
constraints.isTight,父組件傳遞給自身的約束是一個(gè)嚴(yán)格約束。這種情況下即使自身的大小依賴后代元素,但也不會(huì)影響父組件。 -
parent is! RenderObject,說(shuō)明當(dāng)前組件是根組件,即RenderView,它沒有parent。
如果當(dāng)前組件不是relayoutBoundary,那么就把父組件的_relayoutBoundary賦值給自己的relayoutBoundary。
之后如果_needsLayout為false,且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ì)依賴子組件的大小,如果sizedByParent為true,則當(dāng)前組件的大小就不依賴子組件大小了,為了邏輯清晰,F(xiàn)lutter 框架中約定,當(dāng)sizedByParent為true時(shí),確定當(dāng)前組件大小的邏輯應(yīng)抽離到performResize()中,這種情況下performLayout主要的任務(wù)便只有兩個(gè):對(duì)子組件進(jìn)行布局和確定子組件在當(dāng)前組件中的布局起始位置偏移。
回到layout()方法,執(zhí)行performLayout(),performLayout()又回調(diào)用child的layout()方法形成遞歸。之后將_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有兩種類型。
- 容器類
Layer:最常用的是OffsetLayer,通常情況下一個(gè)repaintBoundary就對(duì)應(yīng)一個(gè)OffsetLayer。比方說(shuō)每一個(gè)路由都封裝了一個(gè)repaintBoundary,那么每一個(gè)路由就在一個(gè)單獨(dú)的Layer里。另外容器類Layer可以對(duì)其子Layer整體做一些變換效果,比如剪裁效果(ClipRectLayer、ClipRRectLayer、ClipPathLayer)、過(guò)濾效果(ColorFilterLayer、ImageFilterLayer)、矩陣變換(TransformLayer)、透明變換(OpacityLayer)等。 - 繪制類
Layer:最常用的是PictureLayer,比方說(shuō)我們常用的Text,Image組件等就在PictureLayer里。另外還有TextureLayer和PlatformViewLayer主要用于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)如下圖所示:

執(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è)問題的方法是:
- 創(chuàng)建一個(gè)
TransformLayer(記為TransformLayer1) 添加到Layer樹中,接著創(chuàng)建一個(gè)新的PaintingContext和TransformLayer1綁定。 - 子節(jié)點(diǎn)通過(guò)這個(gè)新的
PaintingContext去繪制。
如下圖所示:

這其實(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,如果是且需要被合成的話,那么先將child的Layer進(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ì)大家有所幫助。