相对于React Native
、Weex
等跨平台框架,Flutter
拥有自己的UI绘制体系,避免了React Native
、Weex
等跨平台框架与Native
系统的桥接,从而更好的提升了性能。
在Flutter
中,UI都是一帧一帧的绘制,但这绘制的背后都会经过如下阶段。
- 动画与微任务阶段,主要是处理动画及执行一系列微任务。
- 构建阶段(build),找出标记为“脏”的节点与布局边界之间的所有节点,并做相应的更新。
- 布局阶段,计算
Widget
的大小及位置的确定。 - compositingBits阶段,重绘之前的预处理操作,检查RenderObject是否需要重绘。
- 绘制阶段,根据
Widget
大小及位置来绘制UI。 - compositing阶段,将UI数据发送给GPU处理。
- semantics阶段,与平台的辅助功能相关。
- finalization阶段,主要是从
Element
树中移除无用的Element
对象及处理绘制结束回调。
1、动画与微任务阶段
【Android|Flutter的绘制流程简述】该阶段主要是处理动画及微任务。先来看动画的处理,在使用动画时,很多时候都会添加一个回调函数来进行状态获取或数据更新,如通过
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
的异步任务体系,因此这里就不再叙述。2、build阶段
在上一阶段执行完毕后,就进入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 {...}
...
}
2.1、重新build Widget对象
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。2.2、标记RenderObject 对于
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
完成后,就进入下一阶段,开始布局。3、layout阶段
在该阶段,会确定每个组件的大小及位置,相当于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
的对象。该对象是一个布局约束,由父传给子,子会根据该对象来决定自己的大小。3.1、标记RenderObject 当大小及位置确定后,就又会对
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提供的控件默认会在需要设置的地方自动设置。4、compositingBits阶段
在组件的大小及位置确定后,就会进入当前阶段。该阶段主要是做一件事,就是将
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 = https://www.it610.com/article/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;
}
5、绘制阶段
经过前面的布局及“脏”
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
。5.1、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;
}
}
5.2、Layer节点的添加
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
树的添加。5.3、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 )—— 图层详解这篇文章。6、其他阶段
6.1、compositing阶段 该阶段主要是将更新后的数据传递给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();
}
}
6.2、semantics阶段 在向GPU发送数据后,
Flutter
还会调用flushSemantics
函数。该函数与系统的辅助功能相关,一般情况下是不做任何处理。6.3、finalization阶段 在该阶段,主要是将
Element
对象从树中移除及处理添加在_postFrameCallbacks
集合中的回调函数。由于该回调函数是在绘制结束时调用,所以在该回调函数中,context已经创建成功。7、总结
可以发现,
Flutter
的UI绘制还是蛮复杂的,涉及到的东西也比较多,如动画的处理、辅助功能、异步任务等。但整体上还是通过Widget
、Element
、RenderObject
这三棵树来操作layer
树实现的UI的绘制。熟悉了这四棵树,也就会对
Flutter
的UI绘制有一个清晰的认识。