相对于React Native
、Weex
等跨平台框架,Flutter
拥有本身的UI绘制体系,避免了React Native
、Weex
等跨平台框架与Native
系统的桥接,从而更好的提高了性能。html
在Flutter
中,UI都是一帧一帧的绘制,但这绘制的背后都会通过以下阶段。node
Widget
的大小及位置的肯定。Widget
大小及位置来绘制UI。Element
树中移除无用的Element
对象及处理绘制结束回调。下面就来分析上述的各个阶段git
该阶段主要是处理动画及微任务。先来看动画的处理,在使用动画时,不少时候都会添加一个回调函数来进行状态获取或数据更新,如经过addListener
、addStatusListener
等函数来添加,而这些回调函数就会在本阶段来执行。具体是在SchedulerBinding
中的handleBeginFrame
函数中实现。github
void handleBeginFrame(Duration rawTimeStamp) {
...
try {
// TRANSIENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.transientCallbacks;
//切换为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 {
//切换为midFrameMicrotasks阶段
_schedulerPhase = SchedulerPhase.midFrameMicrotasks;
}
}
复制代码
_invokeFrameCallback
就会调用在使用动画时注册的回调函数,这里仅执行一次。若是咱们在运行时调用_invokeFrameCallback
函数的代码注释调,那么就没法获取动画的状态,从而影响动画的正确执行。缓存
当回调函数执行完毕后,就会进入微任务阶段,在该阶段会执行一系列微任务,因为这涉及到Flutter
的异步任务体系,所以这里就再也不叙述。数据结构
在上一阶段执行完毕后,就进入build阶段,在该阶段主要是从新构建标记为“脏”的Widget
节点及将须要更新的RenderObject
对象标记为“脏”。app
当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<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.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
节点从树中移除。ide
@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<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
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<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
//根据节点深度进行排序
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绘制有一个清晰的认识。