相对于React Native
、Weex
等跨平台框架,Flutter
拥有自己的UI绘制体系,避免了React Native
、Weex
等跨平台框架与Native
系统的桥接,从而更好的提升了性能。
在Flutter
中,UI都是一帧一帧的绘制,但这绘制的背后都会经过如下阶段。
Widget
的大小及位置的确定。Widget
大小及位置来绘制UI。Element
树中移除无用的Element
对象及处理绘制结束回调。下面就来分析上述的各个阶段
该阶段主要是处理动画及微任务。先来看动画的处理,在使用动画时,很多时候都会添加一个回调函数来进行状态获取或数据更新,如通过addListener
、addStatusListener
等函数来添加,而这些回调函数就会在本阶段来执行。具体是在SchedulerBinding
中的handleBeginFrame
函数中实现。
void handleBeginFrame(Duration rawTimeStamp) {
...
try {
// TRANSIENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.transientCallbacks;
//切换为transientCallbacks阶段
final Map callbacks = _transientCallbacks;
//清空已注册的回调函数
_transientCallbacks = {};
//遍历所有注册的回调方法
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阶段,在该阶段主要是重新构建标记为“脏”的Widget
节点及将需要更新的RenderObject
对象标记为“脏”。
当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 localPostFrameCallbacks =
List.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
节点从树中移除。
@override
void drawFrame() {
...
try {
//如果根结点存在,就重新构建Widget
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
//调用RendererBinding中的drawFrame函数
super.drawFrame();
//移除不再使用的Element节点
buildOwner.finalizeTree();
} finally {...}
...
}
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
对象。一般只要不是增删Widget
,Element
对象都会被重用,从而也就会重用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
函数,然后再调用Element
的updateChild
函数,该函数在Flutter之Widget层级介绍中进行了简单的介绍,就是更新Element
对应的Widget
对象。而在updateChild
函数中又会调用子Element
的update
函数,从而调用子Element
的performRebuild
,然后在调用子Element
的updateChild
、update
函数。以此类推,从而更新其所有子Element
的Widget
对象。
最后就是调用叶子节点的updateRenderObject
函数来更新RenderObject
。在更新RenderObject
对象时,会根据情况来对需要重新布局及重新绘制的RenderObject
对象进行标记。然后等待下一次的Vsync信号时来重新布局及绘制UI。
对于RenderObject
对象,可以通过markNeedsLayout
及markNeedsPaint
来标记是否需要重新布局及重新绘制。但在当前阶段只会调用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
完成后,就进入下一阶段,开始布局。
在该阶段,会确定每个组件的大小及位置,相当于Android中的onMeasure
+onLayout
函数所实现的功能。如果是第一次调用该函数,该阶段就会遍历所有的组件,来确定其大小及位置;否则该阶段就会遍历布局边界内的所有组件,来确定其大小及位置。
当上一阶段中的buildOwner.buildScope(renderViewElement)
函数执行完毕后,就会调用RendererBinding
的drawFrame
函数,该函数实现非常简洁。
//绘制帧
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 dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = [];
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也不是随便确定的,因为在调用RenderObject
的layout
函数时,会传递一个继承自Constraints
的对象。该对象是一个布局约束,由父传给子,子会根据该对象来决定自己的大小。
当大小及位置确定后,就又会对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提供的控件默认会在需要设置的地方自动设置。
在组件的大小及位置确定后,就会进入当前阶段。该阶段主要是做一件事,就是将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
的值会根据isRepaintBoundary
及alwaysNeedsCompositing
来共同判断。
RenderObject() {
//isRepaintBoundary决定当前RenderObject是否与父RenderObject分开绘制,默认为false,其值在当前对象的生命周期内无法修改。也就是判断当前对象是否是绘制边界
//alwaysNeedsCompositing为true表示当前RenderObject会一直重绘,如视频播放,默认为false
_needsCompositing = isRepaintBoundary || alwaysNeedsCompositing;
}
然后在向树中添加或者删除RenderObject
对象时会调用adoptChild
及dropChild
函数,而这两个函数都会调用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 dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = [];
//根据节点深度进行排序
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
。
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;
}
}
previousSibling
与nextSibling
分别是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
树的添加。
_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 )—— 图层详解这篇文章。
该阶段主要是将更新后的数据传递给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();
}
}
在向GPU发送数据后,Flutter
还会调用flushSemantics
函数。该函数与系统的辅助功能相关,一般情况下是不做任何处理。
在该阶段,主要是将Element
对象从树中移除及处理添加在_postFrameCallbacks
集合中的回调函数。由于该回调函数是在绘制结束时调用,所以在该回调函数中,context已经创建成功。
可以发现,Flutter
的UI绘制还是蛮复杂的,涉及到的东西也比较多,如动画的处理、辅助功能、异步任务等。但整体上还是通过Widget
、Element
、RenderObject
这三棵树来操作layer
树实现的UI的绘制。
熟悉了这四棵树,也就会对Flutter
的UI绘制有一个清晰的认识。