RepaintBoundary

UI系统会进行界面的图层划分,可以进行图层复用,减少绘制量,提升绘制性能。而RepaintBoundary是开放给开发者使用的独立图层。

RenderObject有一个isRepaintBoundary属性,该属性决定这个RenderObject重绘时是否独立于其父元素,如果该属性为true,则独立绘制。

如何独立绘制呢?

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

/// PainterContext
void paintChild(RenderObject child, Offset offset) {
assert(() {
debugOnProfilePaint?.call(child);
return true;
}());

if (child.isRepaintBoundary) {
stopRecordingIfNeeded();
_compositeChild(child, offset);
} else {
child._paintWithContext(this, offset);
}
}

void _compositeChild(RenderObject child, Offset offset) {
assert(!_isRecording);
assert(child.isRepaintBoundary);
assert(_canvas == null || _canvas!.getSaveCount() == 1);

// Create a layer for our child, and paint the child into it.
// 给子节点创建一个layer,然后在上面绘制
if (child._needsPaint) {
repaintCompositedChild(child, debugAlsoPaintedParent: true);
} else {
assert(() {
// register the call for RepaintBoundary metrics
child.debugRegisterRepaintBoundaryPaint();
child._layerHandle.layer!.debugCreator = child.debugCreator ?? child;
return true;
}());
}
assert(child._layerHandle.layer is OffsetLayer);
final OffsetLayer childOffsetLayer = child._layerHandle.layer! as OffsetLayer;
childOffsetLayer.offset = offset;
appendLayer(childOffsetLayer);
}

void markNeedsPaint() {
assert(!_debugDisposed);
assert(owner == null || !owner!.debugDoingPaint);
if (_needsPaint)
return;
_needsPaint = true;
//如果RenderObject.isRepaintBoundary 为true,则该RenderObject拥有layer,直接绘制
if (isRepaintBoundary) {
assert(() {
if (debugPrintMarkNeedsPaintStacks)
debugPrintStack(label: 'markNeedsPaint() called for $this');
return true;
}());
// 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) {
//找到最近的layer,绘制
owner!._nodesNeedingPaint.add(this);
owner!.requestVisualUpdate();
}
} else if (parent is RenderObject) {
// 没有自己的layer, 会和一个祖先节点共用一个layer
final RenderObject parent = this.parent! as RenderObject;
parent.markNeedsPaint();
assert(parent == this.parent);
} else {
assert(() {
if (debugPrintMarkNeedsPaintStacks)
debugPrintStack(label: 'markNeedsPaint() called for $this (root of render tree)');
return true;
}());
// If we're the root of the render tree (probably a RenderView),
// 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.
if (owner != null)
// 如果直到根节点也没找到一个Layer,那么便需要绘制自身,因为没有其它节点可以绘制根节点。
owner!.requestVisualUpdate();
}
}

Read More

Paint过程

经过Build流程,Render Tree中绘制相关的基础信息已经完成更新;经过Layout流程,Render Tree中每个节点的大小和位置完成计算与存储,接下来进入Paint流程:基于Layout的信息生成绘制指令。

Render Tree和Layer Tree的对应关系

使用Layer Tree的好处是可以做Paint流程的局部更新。Render Tree中,每个RenderObject对象都拥有一个needsCompositing属性,用于判断自身及子节点是否有一个要去合成的图层,同是还有一个_needsCompositingBitsUpdate字段
用于标记该属性是否需要更新。Flutter在Paint开始前首先会完成needsCompositing属性的更新,然后开始正式绘制。

Read More

Layout过程

1
2
3
4
5
6
7
8
9
10
11
12
13
class RenderBinding {
void drawFrame() {
pipelineOwner.flushLayout();//布局
pipelineOwner.flushCompositingBits();//更新所有节点,计算待绘制区域数据
pipelineOwner.flushPaint();//绘制
if (sendFramesToEngine) {
renderView.compositeFrame(); // 发送数据到GPU线程
pipelineOwner.flushSemantics(); // 更新语义化
_firstFrameSent = true;
}
}
}

过程

标记阶段

markNeedsLayout方法会将当前节点标记为需要Layout。markNeedsLayout方法在很多地方都会被触发,比如UI的高宽发生变化,字体属性发生变化等。

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
class RenderObject {
void markNeedsLayout() {
if (_needsLayout) {//已经标记过
return;
}
if (_relayoutBoundary == null) {//当前节点不是布局边界,父节点受此影响,也需要被标记
_needsLayout = true;
if (parent != null) {
markParentNeedsLayout();
}
return;
}
if (_relayoutBoundary != this) {
markParentNeedsLayout();
} else {
_needsLayout = true;
if (owner != null) {
owner!._nodesNeedingLayout.add(this);
owner!.requestVisualUpdate();//请求刷新
}
}
}

void markParentNeedsLayout() {
_needsLayout = true;
final RenderObject parent = this.parent! as RenderObject;
if (!_doingThisLayoutWithCallback) {
parent.markNeedsLayout();
} else {
}
}

}

Read More

Element

概念

Widget是UI元素的配置数据,Element代表屏幕显示元素。主要作用:

  • 维护这棵Element Tree,根据Widget Tree的变化来更新Element Tree,包括:节点的插入、更新、删除、移动等;
  • 将Widget和RenderObject关联到Element Tree上。

Read More

Build过程

概括

  1. runApp,初始化构建Widget树,Widget通过createElement创建对应Element树,Element通过createRenderObject创建RenderObject树;
  2. 调用setState方法时,会将对应的element添加到dirtyElement队列中;触发WidgetsBinding中的_handleBuildScheduled方法,下一帧drawFrame会调用BuilderOwner的buildScope方法;
  3. 在buildScope方法中,会对dirtyElement队列中的element进行排序,然后逐个调用rebuild方法,触发对应的生命周期方法,State的didChangeDependencies、build等;
  4. 当Widget从树中移除时,会调用deactivate方法,将对应的element添加到inactiveElement队列中;调用unmount方法
Read More