Flutter渲染流程

Flutter启动

main函数就是Flutter app的启动入口,在main函数中调用runApp就可以启动我们的界面了:

1
2
3
void main() {
runApp(const MyApp());
}

下面是runApp的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void runApp(Widget app) {
//binding的初始化
final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
//启动绘制流程
binding
..scheduleAttachRootWidget(binding.wrapWithDefaultView(app))
..scheduleWarmUpFrame();
}

class WidgetsFlutterBinding extends BindingBase
with GestureBinding,
SchedulerBinding,
ServicesBinding,
PaintingBinding,
SemanticsBinding,
RendererBinding,
WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding._instance == null) {
WidgetsFlutterBinding();
}
return WidgetsBinding.instance;
}
}

WidgetsFlutterBinding继承自BindingBase,但通过with语句混入了各种Binding,我们都知道mixin的类是没有构造函数的。ensureInitialized()会调用WidgetsFlutterBinding构造函数,主要实现在BindingBase构造函数中。BindingBase在构造函数中会调用initInstances(),这个函数在各个mixin的Binding类中都有实现,首先调用的是WidgetsBinding的实现,然后一层一层调用super.initInstances(),这样所以Binding类的initInstances()都会被执行。下面是initInstances的调用时序:


zenuml
WidgetsBinding.initInstances {
    RendererBinding.initInstances {
        SemanticsBinding.initInstances {
            PaintingBinding.initInstances {
                ServicesBinding.initInstances {
                    SchedulerBinding.initInstances {
                        GestureBinding.initInstances {
                            return
                        }
                        return
                    }
                    return
                }
                createImageCache()
                return
            }
            return
        }
        createRootPipelineOwner
        return
    }
    "_buildOwner = BuildOwner();"
    "buildOwner!.onBuildScheduled = _handleBuildScheduled;"
}
  • GestureBinding.initInstances:会向PlatformDispatcher设置手势事件的回调,会把从flutter engine接收的数据包解析成PointerEvent,包括PointerDownEvent、PointerUpEvent等;

  • SchedulerBinding提供了scheduleFrame()接口来主动触发一次绘制刷新。并且还会向PlatformDispatcher注册屏幕刷新事件的回调,如下:

    1
    2
    3
    4
    void ensureFrameCallbacksRegistered() {
    platformDispatcher.onBeginFrame ??= _handleBeginFrame;
    platformDispatcher.onDrawFrame ??= _handleDrawFrame;
    }

启动渲染流程

packages/flutter/lib/src/widgets/binding.dart中的WidgetsBinding.drawFrame()的注释中介绍了渲染流程。

FlutterViewrender(Scene scene)RenderObjectRenderViewRenderBoxScenechild RendererBindingdrawFrame()PipelineOwnerRenderViewAbstractNodeRenderObject

绘制UI需要一个画布(Canvas)来进行绘制,然后PictureRecorder会生成一个Picture,并保存到PictureLayer中。

CanvasPictureRecorderPicturePictureLayer

C++层类图

PictureRecorderDisplayListBuilder BeginRecording()Picture endRecording()DisplayListBuilderCanvasPictureDisplayList

PictureRecorder在【flutter_engine】\lib\ui\painting\picture_recorder.h中声明。

RenderObjectWidgetRenderObjectToWidgetAdapterRenderObjectToWidgetElementRenderObjectElementStatelessWidgetViewFlutterViewMyApp用户层根WidgetInheritedWidget_ViewScopeMediaQueryRenderObjectWithChildMixinRenderViewchildchildchildcontainerwith

帧调度

我们在自定义的StatefulWidget调用setState就会触发UI重绘,我们以此为起点进行渲染流程的分析。我们先概述一下绘制的基本流程,然后再进行详细的流程和代码分析。有一个大局观之后再看细节更容易理解:

  1. setState会调用Element.markNeedsBuild(),会调用到SchedulerBinding.scheduleFrame来通知引擎要进行下一帧的绘制,然后把对应的Element添加到_dirtyElements这个需要重绘的脏列表中;
  2. 应用层接收到引擎开始绘制的事件回调(这个可能是vsync信号触发的,后面会详细分析引擎内部的机制),然后就启动了build/layout/paint这个UI渲染流程;
  3. 遍历_dirtyElements脏数据列表,执行Element.rebuild,会导致widget重新build;
  4. 使用新的widget来更新Element,被修改的Element关联的RenderObject会执行markNeedsLayout,用来标记这个RenderObject需要重新布局,markNeedsLayout会向上遍历parent直到遇到布局边界点,并把作为布局边界点的RenderObject添加到PipelineOwner._nodesNeedingLayout列表中;
  5. 遍历_nodesNeedingLayout布局列表,列表中的RenderObject会被执行performLayout()和markNeedsPaint(),performLayout对子树进行布局计算,markNeedsPaint则会递归调用parent,直到遇到绘制边界节点,并把绘制边界节点添加到_nodesNeedingPaint列表中;
  6. 遍历_nodesNeedingPaint列表,对子树进行绘制;

zenuml
State.setState {
    Element.markNeedsBuild {
        "_dirty = true"
        BuildOwner.scheduleBuildFor {
            WidgetsBinding._handleBuildScheduled {
                SchedulerBinding.ensureVisualUpdate {
                    SchedulerBinding.scheduleFrame {
                        "确保onBeginFrame/onDrawFrame回调已设置"
                        PlatformDispatcher.scheduleFrame {
                            "调用engine的native实现"
                            return
                        }
                    }
                    return
                }
                return
            }
            "_dirtyElements.add(element)"
            "element._inDirtyList = true"
            return
        }
        return
    }
}

在调度帧刷新任务阶段,最重要的两个工作就是:

  1. 调用PlatformDispatcher.scheduleFrame,通知引擎要进行下一帧的刷新;
  2. 将状态改变的Element添加到BuildOwner._dirtyElements脏列表中。

在scheduleFrame执行后,引擎就会在合适的时机先后执行onBeginFrameonDrawFrame两个回调,比如下个vsync信号到来时。下面是PlatformDispatcher.scheduleFrame()函数的注释:

Requests that, at the next appropriate opportunity, the [onBeginFrame] and [onDrawFrame] callbacks be invoked.

帧绘制

调度阶段之后,就会执行帧的绘制任务,帧的绘制分为如下几个阶段:

  • idle:空闲阶段。
  • transientCallbacks:临时回调执行阶段。对应SchedulerBinding.handleBeginFrame(即,onBeginFrame),会执行通过WidgetsBinding.instance.scheduleFrameCallback注册的回调;
  • midFrameMicrotasks:执行临时回调期间注册的Microtasks;
  • persistentCallbacks:持久回调执行阶段,持久回调是通过SchedulerBinding.addPersistentFrameCallback来注册。在RendererBinding.initInstances就会注册一个永久回调,此永久回调中会执行RendererBinding.drawFrame()
  • postFrameCallbacks:执行通过SchedulerBinding.addPostFrameCallback注册的回调,这些回调是在一帧渲染完成后被执行。

下面是这五个阶段的时序图:


zenuml
PlatformDispatcher.onBeginFrame {
    SchedulerBinding.handleBeginFrame {
        "🚩SchedulerPhase.transientCallbacks"
        "🚩SchedulerPhase.midFrameMicrotasks"
        return
    }
}
PlatformDispatcher.onDrawFrame {
    SchedulerBinding.handleDrawFrame {
        "🚩SchedulerPhase.persistentCallbacks" {
            WidgetsBinding.drawFrame
        }
        "🚩SchedulerPhase.postFrameCallbacks"
        "🚩SchedulerPhase.idle"
        return
    }
}

其中最重要的就是RendererBinding.drawFrame的执行,build、layout和draw都在这里被执行。下面是WidgetsBinding.drawFrame()的代码实现:

1
2
3
4
5
6
7
8
9
10
11
//WidgetsBinding
void drawFrame() {
try {
if (rootElement != null) {
//执行build和markNeedsLayout
buildOwner!.buildScope(rootElement!);
}
super.drawFrame(); //RendererBinding.drawFrame()
buildOwner!.finalizeTree();
} finally {}
}

zenuml
WidgetsBinding.drawFrame {
    BuildOwner.buildScope {
        "遍历_dirtyElements列表,Element执行rebuild" {
            StatefulElement."Element.rebuild" {
                "ComponentElement:performRebuild" {
                    build {
                        "state.build(this)"
                    }
                    "Element:updateChild"
                }
            }
        }
    }
    RendererBinding."super.drawFrame()" {
        PipelineOwner.flushLayout
        PipelineOwner.flushCompositingBits
        PipelineOwner.flushPaint
        return
    }
}

buildScope并markNeedsLayout

首先遍历BuildOwner._dirtyElements列表中Element,调用这些Element的rebuild方法。rebuild会调用到Widget或者State的build方法,如果某个组件需要更新,则其RenderObject.markNeedsLayout会被执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//BuildOwner
void buildScope(Element context, [ VoidCallback? callback ]) {
if (callback == null && _dirtyElements.isEmpty) {
return;
}
try {
_scheduledFlushDirtyElements = true;
//setState时StatefulElement已经被放到这个_dirtyElements列表中了
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
final Element element = _dirtyElements[index];
try {
//执行rebuild,最终会执行到widget的build方法
element.rebuild();
} catch (e, stack) {}
index += 1;
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
dirtyCount = _dirtyElements.length;
while (index > 0 && _dirtyElements[index - 1].dirty) {
index -= 1;
}
}
}
} finally {
//清除脏标记,以及清空脏列表
for (final Element element in _dirtyElements) {
assert(element._inDirtyList);
element._inDirtyList = false;
}
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
}
}

我们看下element.rebuild()是如何实现的。先看下

Elementvoid rebuild({force})ComponentElementvoid performRebuild()StatefulElementWidget build()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//Element
void rebuild({bool force = false}) {
if (_lifecycleState != _ElementLifecycle.active || (!_dirty && !force)) {
return;
}
try {
performRebuild();
} finally {}
}
//ComponentElement
void performRebuild() {
Widget? built;
try {
built = build();
} catch (e, stack) {
} finally {
// We delay marking the element as clean until after calling build() so
// that attempts to markNeedsBuild() during build() will be ignored.
super.performRebuild(); // clears the "dirty" flag
}
try {
//更新child配置信息,如果wiget配置项发生了改变,会调用到对应RenderObject的额markNeedsLayout
_child = updateChild(_child, built, slot);
} catch (e, stack) { ... }
}
//StatefulElement
@override
Widget build() => state.build(this); //调用用户自定义的build生成Widget

比如setState更新了一个Text的文本内容,这个Text关联的RenderObject就会调用到RenderObject.markNeedsLayout,用来标记这个RenderObject需要重新布局,markNeedsLayout会向上回溯parent直到遇到布局边界点,并把作为布局边界点的RenderObject添加到PipelineOwner._nodesNeedingLayout列表中。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//RenderObject
void markNeedsLayout() {
if (_needsLayout) {
return;
}
if (_relayoutBoundary == null) {
_needsLayout = true;
if (parent != null) {
// _relayoutBoundary is cleaned by an ancestor in RenderObject.layout.
// Conservatively mark everything dirty until it reaches the closest
// known relayout boundary.
markParentNeedsLayout();
}
return;
}
if (_relayoutBoundary != this) {
//parent的_needsLayout会设置为true,并调用parent.markNeedsLayout()
//因此,这句代码的作用是在parent方向上将自身到布局边界路径上的所有节点都标记为需要布局,
//并且把找到的边界节点添加到PipelineOwner中(见下面的else代码分支)
markParentNeedsLayout();
} else {
//当前节点就是_relayoutBoundary的情况就会停止向上遍历
_needsLayout = true;
if (owner != null) {
//添加到PipelineOwner中
owner!._nodesNeedingLayout.add(this);
//persistentCallbacks阶段,此调用不起任何作用,具体可以看代码实现
owner!.requestVisualUpdate();
}
}
}

注意:这里的布局边界和绘制边界的区别。如果每次更新UI要对整个树重新布局,这样无疑会到了很大的性能负担,而布局边界就是为了解决这个问题。什么样的节点是边界节点呢?我们会在layout阶段详细介绍。

flushLayout

Flutter的布局过程:

  • 首先,上层 widget 向下层 widget 传递约束条件;
  • 然后,下层 widget 向上层 widget 传递大小信息。
  • 最后,上层 widget 决定下层 widget 的位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//PipelineOwner
void flushLayout() {
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) {
//执行RenderObject的performLayout()和markNeedsPaint()
node._layoutWithoutResize();
}
}
// No need to merge dirty nodes generated from processing the last
// relayout boundary back.
_shouldMergeDirtyNodes = false;
}

for (final PipelineOwner child in _children) {
child.flushLayout();
}
} finally {
_shouldMergeDirtyNodes = false;
}
}

flushLayout时,会遍历_nodesNeedingLayout列表中的RenderObject,然后执行其performLayout()和markNeedsPaint()。performLayout是在子类中实现,比如RenderPositionedBox.performLayout()的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//RenderPositionedBox
void performLayout() {
//获取parent传下来的布局约束
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,把布局约束传给child;
//parentUsesSize为true表示需要依赖child的大小进行布局
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,
));
//计算child的位置,位置放在child!.parentData.offset
alignChild();
} else {
size = constraints.constrain(Size(
shrinkWrapWidth ? 0.0 : double.infinity,
shrinkWrapHeight ? 0.0 : double.infinity,
));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//RenderObject
void layout(Constraints constraints, { bool parentUsesSize = false }) {
//判断是否是边界节点:
//1. parentUsesSize为false,表示当前组件的parent的大小不依赖这个child组件,所以当前组件需要重新布局的时候不需要对parent进行重新布局;
//2. sizedByParent为true,当前组件的大小只依赖parent的约束,而不受当前组件的子组件大小的影响。比如RenderAndroidView是嵌入的
// Android原生View,不可能再嵌套子孙widget的,大小只受父布局的约束;RenderTwoDimensionalViewport也是一个示例场景;
//3. constraints.isTight严格约束,固定宽高,比如指定宽高的SizedBox
//4. parent不是RenderObject,只有根组件RenderView没有parent
final bool isRelayoutBoundary = !parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject;
//如果符合布局边界条件,则使用当前组件为布局边界,否则使用parent的布局边界
final RenderObject relayoutBoundary = isRelayoutBoundary ? this : parent!._relayoutBoundary!;

if (!_needsLayout && constraints == _constraints) {
if (relayoutBoundary != _relayoutBoundary) {
//如果布局边界发生了变化,则递归访问子孙组件,将子孙的_relayoutBoundary设置为新的布局边界;
//递归终止条件是,子孙组件自身就是边界节点。
_relayoutBoundary = relayoutBoundary;
visitChildren(_propagateRelayoutBoundaryToChild);
}
return;
}
_constraints = constraints;
if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
// The local relayout boundary has changed, must notify children in case
// they also need updating. Otherwise, they will be confused about what
// their actual relayout boundary is later.
//递归清除子孙组件的_relayoutBoundary,递归终止条件是子孙组件自身就是边界节点
visitChildren(_cleanChildRelayoutBoundary);
}
_relayoutBoundary = relayoutBoundary;

if (sizedByParent) {
try {
performResize();
} catch (e, stack) { ... }
}
RenderObject? debugPreviousActiveLayout;
try {
performLayout(); //执行布局算法
markNeedsSemanticsUpdate();
} catch (e, stack) { ... }
_needsLayout = false; //清空布局标记
//标记是否进行重新绘制,重新绘制的节点会被添加到PipelineOwner._nodesNeedingPaint列表中
markNeedsPaint();
}

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 we always have our own layer, then we can just repaint
// ourselves without involving any other nodes.
assert(_layerHandle.layer is OffsetLayer);
if (owner != null) {
//当前节点是绘制边界节点,把边界节点加入到重绘列表中
owner!._nodesNeedingPaint.add(this);
owner!.requestVisualUpdate(); //idle或postFrameCallbacks阶段会调用scheduleFrame,其他阶段无作用
}
} else if (parent is RenderObject) {
//递归调用parent,直到遇到绘制边界节点
parent!.markNeedsPaint();
} else {
// If we are the root of the render tree and not a repaint boundary
// then we have to paint ourselves, since nobody else can paint us.
// We don't add ourselves to _nodesNeedingPaint in this case,
// because the root is always told to paint regardless.
//
// Trees rooted at a RenderView do not go through this
// code path because RenderViews are repaint boundaries.
if (owner != null) {
owner!.requestVisualUpdate();
}
}
}

再总结一下,flushLayout阶段会遍历PipelineOwner._nodesNeedingPaint中的RenderObject,然后调用它们的performLayout()和markNeedsPaint(),markNeedsPaint()会把需要绘制的边界节点添加到PipelineOwner._nodesNeedingPaint中。

flushPaint

PipelineOwner.flushPaint会遍历_nodesNeedingPaint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//PipelineOwner
void flushPaint() {
try {
final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[]; //_nodesNeedingPaint赋值为空

// Sort the dirty nodes in reverse order (deepest first).
for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
//能够加入_nodesNeedingPaint肯定都是绘制边界节点
if ((node._needsPaint || node._needsCompositedLayerUpdate) && node.owner == this) {
if (node._layerHandle.layer!.attached) {
assert(node.isRepaintBoundary);
if (node._needsPaint) {
PaintingContext.repaintCompositedChild(node); //内部直接调用_repaintCompositedChild
} else {
PaintingContext.updateLayerProperties(node);
}
} else {
node._skippedPaintingOnLayer();
}
}
}
for (final PipelineOwner child in _children) {
child.flushPaint();
}
} finally { ... }
}

//PaintingContext的静态函数
static void _repaintCompositedChild(
RenderObject child, {
bool debugAlsoPaintedParent = false,
PaintingContext? childContext,
}) {
assert(child.isRepaintBoundary); //必须是绘制边界节点
OffsetLayer? childLayer = child._layerHandle.layer as OffsetLayer?;
if (childLayer == null) {
// 为这个绘制边界节点创建一个OffsetLayer,也有可能是OffsetLayer的子类
final OffsetLayer layer = child.updateCompositedLayer(oldLayer: null);
child._layerHandle.layer = childLayer = layer;
} else {
childLayer.removeAllChildren();
//childLayer不为空的情况,此处返回的必须还是childLayer,后面有个assert来检查
final OffsetLayer updatedLayer = child.updateCompositedLayer(oldLayer: childLayer);
assert(identical(updatedLayer, childLayer),
'$child created a new layer instance $updatedLayer instead of reusing the '
'existing layer $childLayer. See the documentation of RenderObject.updateCompositedLayer '
'for more information on how to correctly implement this method.'
);
}
child._needsCompositedLayerUpdate = false;

assert(identical(childLayer, child._layerHandle.layer));
assert(child._layerHandle.layer is OffsetLayer); //必须是OffsetLayer

childContext ??= PaintingContext(childLayer, child.paintBounds);
child._paintWithContext(childContext, Offset.zero);

// Double-check that the paint method did not replace the layer (the first
// check is done in the [layer] setter itself).
assert(identical(childLayer, child._layerHandle.layer));
childContext.stopRecordingIfNeeded();
}

//RenderObject
void _paintWithContext(PaintingContext context, Offset offset) {
// If we still need layout, then that means that we were skipped in the
// layout phase and therefore don't need painting. We might not know that
// yet (that is, our layer might not have been detached yet), because the
// same node that skipped us in layout is above us in the tree (obviously)
// and therefore may not have had a chance to paint yet (since the tree
// paints in reverse order). In particular this will happen if they have
// a different layer, because there's a repaint boundary between us.
if (_needsLayout) {
return;
}
RenderObject? debugLastActivePaint;
_needsPaint = false;
_needsCompositedLayerUpdate = false;
_wasRepaintBoundary = isRepaintBoundary;
try {
//RenderObject执行paint进行绘制,需要子类来实现paint方法
paint(context, offset);
assert(!_needsLayout); // check that the paint() method didn't mark us dirty again
assert(!_needsPaint); // check that the paint() method didn't mark us dirty again
} catch (e, stack) {... }
}

//RenderShiftedBox
void paint(PaintingContext context, Offset offset) {
final RenderBox? child = this.child;
if (child != null) {
final BoxParentData childParentData = child.parentData! as BoxParentData;
context.paintChild(child, childParentData.offset + offset);
}
}

RenderObject.paint需要子类自己实现,我们来看一个简单的样例,来自RenderShiftedBox的实现,它是具备child的节点,所以会递归进行child的绘制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//RenderShiftedBox
void paint(PaintingContext context, Offset offset) {
final RenderBox? child = this.child;
if (child != null) {
final BoxParentData childParentData = child.parentData! as BoxParentData;
//只有一个child,调用paintChild进行绘制
context.paintChild(child, childParentData.offset + offset);
}
}
//PaintingContext
void paintChild(RenderObject child, Offset offset) {
if (child.isRepaintBoundary) {
stopRecordingIfNeeded();
//child是绘制边界节点,会创建新的PaintingContext和OffsetLayer进行绘制
_compositeChild(child, offset);
// If a render object was a repaint boundary but no longer is one, this
// is where the framework managed layer is automatically disposed.
} else if (child._wasRepaintBoundary) {
//child曾经是个绘制边界,但现在已经不是了,则需要取消与Layer的关联
//如果layer引用计数变为0了,则会调用Layer.dispose来释放资源
child._layerHandle.layer = null;
//执行child.paint
child._paintWithContext(this, offset);
} else {
//执行child.paint
child._paintWithContext(this, offset);
}
}

再来看个真正的绘制,代码示例来自ColoredBox,对应的render object是_RenderColoredBox,绘制代码如下:

1
2
3
4
5
6
7
8
9
10
void paint(PaintingContext context, Offset offset) {
if (size > Size.zero) {
//使用Canvas进行矩形区域的绘制
context.canvas.drawRect(offset & size, Paint()..color = color);
}
if (child != null) {
//child不为空,继续继续child的绘制
context.paintChild(child!, offset);
}
}

Canvas绘制

LayerPicturePictureRecorderPicture endRecording()PictureLayerPaintingContextCanvasSceneBuilderScene build()SceneFlutterViewrender(Scene scene)
  • RenderView.compositeFrame上屏:先通过layer构建Scene,然后再通过FlutterView.render(scene)来渲染,后面的流程就是c++的native流程了。

参考资料

Flutter实战·第二版–Flutter核心原理
Flutter渲染之绘制上屏