1. 动画与微任务阶段:
主要是处理动画及执行一系列微任务。具体是在SchedulerBinding中的handleBeginFrame函数中实现。
ini
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;
}
}
2. 构建阶段(build):
在该阶段主要是重新构建标记为"脏"的Widget节点及将需要更新的RenderObject对象标记为"脏"。
当handleBeginFrame函数执行完毕后,就会执行handleDrawFrame函数,该函数在SchedulerBinding对象初始化时会与Window相关联,所以除第一次需要主动调用外,其他时候皆是通过Window来调用该函数。
ini
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对象初始化时注册的。主要函数:persistentCallbacks
scss
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.
}
}
在WidgetsBinding中重写了drawFrame函数。在该函数中会创建新的Widget对象替换旧的Widget对象并将不需要的Element节点从树中移除。
typescript
@override
void drawFrame() {
...
try {
//如果根结点存在,就重新构建Widget
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
//调用RendererBinding中的drawFrame函数
super.drawFrame();
//移除不再使用的Element节点
buildOwner.finalizeTree();
} finally {...}
...
}
2.1. 重新build Widget对象:
ini
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函数。
scala
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函数,就是更新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函数。
java
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. 布局阶段:
计算Widget的大小及位置的确定。
在该阶段,会确定每个组件的大小及位置,相当于Android中的onMeasure+onLayout函数所实现的功能。如果是第一次调用该函数,该阶段就会遍历所有的组件,来确定其大小及位置;否则该阶段就会遍历布局边界内的所有组件,来确定其大小及位置。
当上一阶段中的buildOwner.buildScope(renderViewElement)函数执行完毕后,就会调用RendererBinding的drawFrame函数,该函数实现非常简洁。
scss
//绘制帧
void drawFrame() {
//对指定组件及其子组件进行大小测量及位置确定
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
renderView.compositeFrame();
pipelineOwner.flushSemantics(); }
其中flushLayout就是进行组件的大小及位置确定,在该函数中会遍历集合_nodesNeedingLayout并调用集合中每个对象的_layoutWithoutResize函数。
javascript
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函数是私有的,所以不存在重写的问题。那么就直接来看该函数。
scss
void _layoutWithoutResize() {
try {
performLayout();
markNeedsSemanticsUpdate();
} catch (e, stack) {...}
_needsLayout = false;
markNeedsPaint();
}
_layoutWithoutResize函数很简单,就直接调用了performLayout函数。
当然,RenderObject对象的size也不是随便确定的,因为在调用RenderObject的layout函数时,会传递一个继承自Constraints的对象。该对象是一个布局约束,由父传给子,子会根据该对象来决定自己的大小。
3.1. 标记RenderObject
当大小及位置确定后,就又会对RenderObject进行一次标记,这次跟上一阶段的标记大同小异,但这次是标记可绘制的RenderObject对象,然后在后面对这些对象进行重新绘制。标记可绘制的RenderObject对象是通过markNeedsPaint函数来实现的,代码如下。
csharp
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对象标记为"脏",方便在下一阶段对这些RenderObject对象进行重绘。具体代码实现是在flushCompositingBits函数中,该函数在Layout阶段后立即调用。
scss
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来共同判断。
bash
RenderObject() {
//isRepaintBoundary决定当前RenderObject是否与父RenderObject分开绘制,默认为false,其值在当前对象的生命周期内无法修改。也就是判断当前对象是否是绘制边界
//alwaysNeedsCompositing为true表示当前RenderObject会一直重绘,如视频播放,默认为false
_needsCompositing = isRepaintBoundary || alwaysNeedsCompositing;
}
然后在向树中添加或者删除RenderObject对象时会调用adoptChild及dropChild函数,而这两个函数都会调用markNeedsCompositingBitsUpdate函数,也就在markNeedsCompositingBitsUpdate函数内完成了将当前对象添加到集合中的操作。
scss
//向树中添加当前节点
@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函数来判断是否将这些对象及子对象标记为"脏",然后在下一阶段进行绘制。
ini
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. 绘制阶段:
根据Widget大小及位置来绘制UI。
通过调用flushPaint函数就可以重绘已经标记的"脏"RenderObject对象及其子对象。
javascript
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函数的实现。
scss
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();
}
5.1. Layer
在该函数中主要是对Layer对象的处理,然后调用_paintWithContext函数,在_paintWithContext函数中就会调用paint这个函数,从而实现UI的绘制。至此,就完成了UI的绘制,下面再来看一个被忽略的对象------Layer。
Layer是"图层"意思,在Flutter中是最容易被忽略但又无比重要的一个类。它非常贴近底层,可以很容易的看到调用Native方法。
Layer跟其他三棵树一样,也是一棵树,有"脏"状态的标记、更新等操作。不同的是,Layer是一个双链表结构,在每个Layer对象中都会指向其前置节点与后置节点(叶子Layer的后置节点为null)。
java
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设置为父节点的后置节点,父节点设置为当前节点的前置节点。这样,就形成了一颗树。
ini
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的状态更新
ini
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的状态。
6. compositing阶段:
将UI数据发送给GPU处理。
该阶段主要是将更新后的数据传递给GPU。这时候调用的是compositeFrame函数,该函数很简单,就是调用了一个Native函数。
scss
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();
}
}
7. semantics阶段:
与平台的辅助功能相关。
在向GPU发送数据后,Flutter还会调用flushSemantics函数。该函数与系统的辅助功能相关,一般情况下是不做任何处理。
8. finalization阶段:
主要是从Element树中移除无用的Element对象及处理绘制结束回调。
在该阶段,主要是将Element对象从树中移除及处理添加在_postFrameCallbacks集合中的回调函数。由于该回调函数是在绘制结束时调用,所以在该回调函数中,context已经创建成功。
其他:
flushLayout触发布局,将RenderObject树的dirty节点通过调用performLayout方法进行逐一布局,我们先看一下RenderPadding中的实现
ini
@override
void performLayout() {
_resolve();//解析padding参数
if (child == null) {//如果没有child,直接将constraints与padding综合计算得出自己的size
size = constraints.constrain(Size(
_resolvedPadding.left + _resolvedPadding.right,
_resolvedPadding.top + _resolvedPadding.bottom
));
return;
}
final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding);//将padding减去,生成新的约束innerConstraints
child.layout(innerConstraints, parentUsesSize: true);//用新的约束去布局child
final BoxParentData childParentData = child.parentData;
childParentData.offset = Offset(_resolvedPadding.left, _resolvedPadding.top);//设置childParentData的offset值
size = constraints.constrain(Size(//将constraints与padding以及child的sieze综合计算得出自己的size
_resolvedPadding.left + child.size.width + _resolvedPadding.right,
_resolvedPadding.top + child.size.height + _resolvedPadding.bottom
));
}
可以看到RenderPadding中的布局分两种情况。如果没有child,那么就直接拿parent传过来的约束以及padding来确定自己的大小;否则就先去布局child,让后再拿parent传过来的约束和padding以及child的size来确定自己的大小。RenderPadding是典型的单child的RenderBox,我们看一下多个child的RenderBox。例如RenderFlow
ini
@override
void performLayout() {
size = _getSize(constraints);//直接先确定自己的size
int i = 0;
_randomAccessChildren.clear();
RenderBox child = firstChild;
while (child != null) {//遍历孩子
_randomAccessChildren.add(child);
final BoxConstraints innerConstraints = _delegate.getConstraintsForChild(i, constraints);//获取child的约束,此方法为抽象
child.layout(innerConstraints, parentUsesSize: true);//布局孩子
final FlowParentData childParentData = child.parentData;
childParentData.offset = Offset.zero;
child = childParentData.nextSibling;
i += 1;
}
}
可以看到RenderFlow的size直接就根据约束来确定了,并没去有先布局孩子,所以RenderFlow的size不依赖与孩子,后面依旧是对每一个child依次进行布局。
还有一种比较典型的树尖类型的RenderBox,LeafRenderObjectWidget子类创建的RenderObject对象都是,他们没有孩子,他们才是最终需要渲染的对象,例如
arduino
@override
void performLayout() {
size = _sizeForConstraints(constraints);
}
非常简单就通过约束确定自己的大小就结束了。所以performLayout过程就是两点,确定自己的大小以及布局孩子。我们上面提到的都是RenderBox的子类,这些RenderObject约束都是通过BoxConstraints来完成,但是RenderSliver的子类的约束是通过SliverConstraints来完成,虽然他们对child的约束方式不同,但他们在布局过程需要执行的操作都是一致的。
绘制
布局完成了,PipelineOwner就通过flushPaint来进行绘制
ini
void flushPaint() {
try {
final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// 对dirty nodes列表进行排序,最深的在第一位
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
assert(node._layer != null);
if (node._needsPaint && node.owner == this) {
if (node._layer.attached) {
PaintingContext.repaintCompositedChild(node);
} else {
node._skippedPaintingOnLayer();
}
}
}
} finally {}
}
PaintingContext.repaintCompositedChild(node)会调用到child._paintWithContext(childContext, Offset.zero)方法,进而调用到child的paint方法,我们来看一下第一次绘制的情况,dirty的node就应该是RenderView,跟进RenderView的paint方法
typescript
@override
void paint(PaintingContext context, Offset offset) {
if (child != null)
context.paintChild(child, offset);//直接绘制child
}
自己没有什么绘制的内容,直接绘制child,再看一下RenderShiftedBox
java
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
final BoxParentData childParentData = child.parentData;
context.paintChild(child, childParentData.offset + offset);//直接绘制child
}
}
好像没有绘制内容就直接递归的进行绘制child,那找一个有绘制内容的吧,我们看看RenderDecoratedBox
scss
@override
void paint(PaintingContext context, Offset offset) {
_painter ??= _decoration.createBoxPainter(markNeedsPaint);//获取painter画笔
final ImageConfiguration filledConfiguration = configuration.copyWith(size: size);
if (position == DecorationPosition.background) {//画背景
_painter.paint(context.canvas, offset, filledConfiguration);//绘制过程,具体细节再painter中
if (decoration.isComplex)
context.setIsComplexHint();
}
super.paint(context, offset);//画child,里面直接调用了paintChild
if (position == DecorationPosition.foreground) {//画前景
_painter.paint(context.canvas, offset, filledConfiguration);
if (decoration.isComplex)
context.setIsComplexHint();
}
}
如果自己有绘制内容,paint方法中的实现就应该包括绘制自己以及绘制child,如果没有孩子就只绘制自己的内容,看一下RenderImage
less
@override
void paint(PaintingContext context, Offset offset) {
if (_image == null)
return;
_resolve();
paintImage(//直接绘制Image,具体细节再此方法中
canvas: context.canvas,
rect: offset & size,
image: _image,
scale: _scale,
colorFilter: _colorFilter,
fit: _fit,
alignment: _resolvedAlignment,
centerSlice: _centerSlice,
repeat: _repeat,
flipHorizontally: _flipHorizontally,
invertColors: invertColors,
filterQuality: _filterQuality
);
}
所以基本上绘制需要完成的流程就是,如果自己有绘制内容,paint方法中的实现就应该包括绘制自己以及绘制child,如果没有孩子就只绘制自己的内容,流程比较简单。
开始执行Dart代码,经历Layout、Paint等过程,生成一棵Layer Tree,将绘制指令保存在Layer中,接着进行栅格化和合成上屏。