w4lle's Notes

人生如逆旅,我亦是行人。

w4lle's avatar w4lle

Flutter UI 渲染浅析(六)Paint

系列文章的第六篇,本篇文章继续分析下Paint绘制过程及Layer Tree。

前面的文章分析完了flushLayout,继续分析下 RendererBinding.drawFrame() 剩余部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
// lib/src/rendering/binding.dart
@protected
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
if (sendFramesToEngine) {
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
_firstFrameSent = true;
}
}

1、flushCompositingBits 标记合成阶段

照例分为两部分,标脏&数据处理。

1.1、markNeedsCompositingBitsUpdate 标脏

当RenderObject获取新的子节点或重新挂载时时,会触发adoptChild

1
2
3
4
5
6
7
8
9
10
11
// lib/src/rendering/object.dart
@override
void adoptChild(RenderObject child) {
assert(_debugCanPerformMutations);
assert(child != null);
setupParentData(child);
markNeedsLayout();
markNeedsCompositingBitsUpdate();
markNeedsSemanticsUpdate();
super.adoptChild(child);
}

其中的markNeedsCompositingBitsUpdate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// lib/src/rendering/object.dart
void markNeedsCompositingBitsUpdate() {
if (_needsCompositingBitsUpdate)
return;
_needsCompositingBitsUpdate = true;
if (parent is RenderObject) {
final RenderObject parent = this.parent as RenderObject;
if (parent._needsCompositingBitsUpdate)
return;
//向上寻找第一个isRepaintBoundary为true的节点
if (!isRepaintBoundary && !parent.isRepaintBoundary) {
parent.markNeedsCompositingBitsUpdate();
return;
}
}
...
if (owner != null)
owner._nodesNeedingCompositingBitsUpdate.add(this);
}

做了几件事情:

  • 标记_needsCompositingBitsUpdate为true
  • 加入到pipelineOwner的_nodesNeedingCompositingBitsUpdate列表中
  • 向上寻找第一个isRepaintBoundary为true的节点,递归结束

所以就是把所有不是isRepaintBoundary的RenderObject都加入到了_nodesNeedingCompositingBitsUpdate列表中。

1.2、flushCompositingBits

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// lib/src/rendering/binding.dart
void flushCompositingBits() {
if (!kReleaseMode) {
//记录 Compositing bits
Timeline.startSync('Compositing bits');
}
//广度优先
_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
for (final RenderObject node in _nodesNeedingCompositingBitsUpdate) {
if (node._needsCompositingBitsUpdate && node.owner == this)
node._updateCompositingBits();
}
//清除列表
_nodesNeedingCompositingBitsUpdate.clear();
if (!kReleaseMode) {
//结束记录
Timeline.finishSync();
}
}

顺序遍历 _nodesNeedingCompositingBitsUpdate列表,即先遍历父节点,调用RenderObject的 _updateCompositingBits 方法。

过程记录在TimeLine的Compositing bits阶段,运行在UI线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// lib/src/rendering/object.dart
void _updateCompositingBits() {
if (!_needsCompositingBitsUpdate)
return;
final bool oldNeedsCompositing = _needsCompositing;
_needsCompositing = false;
//递归子节点
visitChildren((RenderObject child) {
child._updateCompositingBits();
if (child.needsCompositing)
_needsCompositing = true;
});
//如果是isRepaintBoundary,标记_needsCompositing为true
if (isRepaintBoundary || alwaysNeedsCompositing)
_needsCompositing = true;
if (oldNeedsCompositing != _needsCompositing)
markNeedsPaint();
_needsCompositingBitsUpdate = false;
}

递归遍历子节点:

  • 找到isRepaintBoundary为true的节点,标记_needsCompositing为true
  • isRepaintBoundary为true节点的所有父节点,都标记_needsCompositing为true

这些标记在flushPaint过程中会用到。

2、isRepaintBoundary

那么isRepaintBoundary是哪里来的?

1
2
3
4
5
6
7
8
9
10
// lib/src/rendering/object.dart
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
/// Initializes internal fields for subclasses.
RenderObject() {
_needsCompositing = isRepaintBoundary || alwaysNeedsCompositing;
}
...
//默认false
bool get isRepaintBoundary => false;
}

isRepaintBoundary可以被重写,Dart Framework有以下这些RenderObject重写了该值并返回了true,其中包含根节点RenderView和RenderRepaintBoundary。

1
2
3
4
5
6
7
8
class RenderRepaintBoundary extends RenderProxyBox {
/// Creates a repaint boundary around [child].
RenderRepaintBoundary({ RenderBox child }) : super(child);
@override
bool get isRepaintBoundary => 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
class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
RenderView({
RenderBox child,
@required ViewConfiguration configuration,
@required ui.Window window,
}) : assert(configuration != null),
_configuration = configuration,
_window = window {
this.child = child;
}
@override
bool get isRepaintBoundary => true;
//RendererBinding的initInstances方法调用该初始化方法
void prepareInitialFrame() {
scheduleInitialLayout();
scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
}
TransformLayer _updateMatricesAndCreateNewRootLayer() {
_rootTransform = configuration.toMatrix();
//RenderView的Layer类型为TransformLayer,它是OffsetLayer的子类
final TransformLayer rootLayer = TransformLayer(transform: _rootTransform);
rootLayer.attach(this);
return rootLayer;
}
..
}

所以除了根节点RenderView外,所有位置在上图中之上的RenderObject节点,都会被标记为_needsCompositing需要合成。

另外,RenderRepaintBoundary对应的Widget是RepaintBoundary,该Widget允许开发者指定图层的绘制层级,也就是绘制边界,用于提高绘制性能。

3、flushPaint 绘制阶段

该阶段主要处理RenderObject的绘制过程。

照例分为两步,标脏&数据处理。

先看一下上文中频繁被调用的markNeedsPaint方法

3.1、markNeedsPaint 标记

上面layout阶段用到了markNeedsPaint()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// lib/src/rendering/object.dart
void markNeedsPaint() {
if (_needsPaint)
return;
_needsPaint = true;
//如果是isRepaintBoundary,加入到_nodesNeedingPaint列表中
if (isRepaintBoundary) {
...
if (owner != null) {
owner._nodesNeedingPaint.add(this);
//发起更新请求
owner.requestVisualUpdate();
}
} else if (parent is RenderObject) {
final RenderObject parent = this.parent as RenderObject;
parent.markNeedsPaint();
assert(parent == this.parent);
} else {
...
if (owner != null)
owner.requestVisualUpdate();
}
}

做了几件事情:

  • 如果是isRepaintBoundary,加入到pipelineOwner的_nodesNeedingPaint列表中,并且发起绘制请求,注册VSYNC信号回调,结束流程
  • 否则向上遍历
  • 如果都不满足,那么只绘制自己,触发绘制frame

isRepaintBoundary可以理解为是否需要独立绘制,如果为true,那么就独立绘制,false就和父节点一起绘制。

下文会详细说明。

通过上面的分析,也就是说只有isRepaintBoundary为true的RenderObject才会被加入到pipelineOwner的_nodesNeedingPaint列表中。

当向上找到isRepaintBoundary时,触发该节点的子树独立绘制流程,流程结束。

3.2、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
void flushPaint() {
if (!kReleaseMode) {
//记录Paint过程
Timeline.startSync('Paint', arguments: timelineArgumentsIndicatingLandmarkEvent);
}
...
try {
final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
//清空列表
_nodesNeedingPaint = <RenderObject>[];
// 深度优先逆序遍历节点
for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
if (node._needsPaint && node.owner == this) {
if (node._layer.attached) {
PaintingContext.repaintCompositedChild(node);
} else {
node._skippedPaintingOnLayer();
}
}
}
} finally {
if (!kReleaseMode) {
//结束记录
Timeline.finishSync();
}
}
}

运行在UI线程,TimeLine记录Paint过程。

从叶子节点逆序遍历 _nodesNeedingPaint列表。

注意这里只有isRepaintBoundary为true的RenderObject才会被加入到pipelineOwner的 _nodesNeedingPaint列表中。

也就是说从下到上寻找绘制边界,然后从绘制边界向下绘制。

继续这个过程直到_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
// lib/src/rendering/object.dart
class PaintingContext extends ClipContext {
//通常只会被 PaintingContext.repaintCompositedChild 和pushLayer 调用
@protected
PaintingContext(this._containerLayer, this.estimatedBounds)
static void repaintCompositedChild(RenderObject child, { bool debugAlsoPaintedParent = false }) {
_repaintCompositedChild(
child,
debugAlsoPaintedParent: debugAlsoPaintedParent,
);
}
static void _repaintCompositedChild(
RenderObject child, {
bool debugAlsoPaintedParent = false,
PaintingContext childContext,
}) {
OffsetLayer childLayer = child._layer as OffsetLayer;
if (childLayer == null) {
//RenderObject的默认Layer类型是OffsetLayer,记录坐标相对偏移值
child._layer = childLayer = OffsetLayer();
} else {
childLayer.removeAllChildren();
}
//构建一个新的PaintingContext
childContext ??= PaintingContext(child._layer, child.paintBounds);
//开始绘制,默认Offset偏移量是0
child._paintWithContext(childContext, Offset.zero);
//
childContext.stopRecordingIfNeeded();
}
...
}

PaintingContext作为canvas的持有者,作为参数传递给RenderObject,这样RenderObject对象通过context.canvas可以取到Canvas对象,可以调用canvas相关API实现绘制操作。

1
2
3
4
5
6
7
8
9
10
11
12
// lib/src/rendering/object.dart RenderObject
void _paintWithContext(PaintingContext context, Offset offset) {
...
//_needsPaint置为false,当遍历到更高节点向下绘制时起到隔离作用
_needsPaint = false;
try {
paint(context, offset);
}
...
}
void paint(PaintingContext context, Offset offset) { }

首先把_needsPaint置为false,当遍历到更高节点向下绘制时起到隔离作用,后面详细讲。

RenderObject的paint()方法是一个抽象方法,需要子类去实现。

如果一个RenderObject含有子节点,那么除了自身可能需要绘制外,还需要遍历子节点进行绘制。

以RenderStack为例看下实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// lib/src/rendering/stack.dart RenderStack
@override
void paint(PaintingContext context, Offset offset) {
...
paintStack(context, offset);
}
@protected
void paintStack(PaintingContext context, Offset offset) {
defaultPaint(context, offset);
}
void defaultPaint(PaintingContext context, Offset offset) {
ChildType child = firstChild;
while (child != null) {
final ParentDataType childParentData = child.parentData as ParentDataType;
context.paintChild(child, childParentData.offset + offset);
child = childParentData.nextSibling;
}
}

由于RenderStack本身没有内容需要绘制,所以直接遍历子节点调用context.paintChild方法绘制子节点。

同时将Layout阶段子节点存储的位置和大小信息parentData取出来,加上自身偏移传递给子节点,parentData是layout阶段计算出的位置信息。

所以通过 paint()-> paintChild() -> paint() ->stopRecordingIfNeeded() …调用栈完成局部树的刷新。

为什么说是局部树 ?因为有isRepaintBoundary的存在,每个局部树代表一个图层,下面详细分析。

4、LayerTree

4.1、RepaintBoundary 绘制边界

当_nodesNeedingPaint列表中,深度优先的节点绘制完成后,调用childContext.stopRecordingIfNeeded()方法完成当前局部树的绘制,并记录在Flutter Engine对应Layer的Picture对象中(这块后面再分析)。

然后继续遍历_nodesNeedingPaint中的节点。

由于是深度优先,所以后续被遍历到的节点,可能是已完成绘制节点的祖先节点,那么再去paintChild,会不会再次触发已完成绘制节点的绘制动作?

答案是不会,因为有isRepaintBoundary的存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// lib/src/rendering/object.dart PaintingContext
void paintChild(RenderObject child, Offset offset) {
if (child.isRepaintBoundary) {
//结束绘制,记录到Picture
stopRecordingIfNeeded();
//合成子Layer
_compositeChild(child, offset);
} else {
//使用相同PaintingContext绘制子节点
child._paintWithContext(this, offset);
}
}
void _compositeChild(RenderObject child, Offset offset) {
// 如果需要绘制,为子节点新建一个layer并绘制
if (child._needsPaint) {
repaintCompositedChild(child, debugAlsoPaintedParent: true);
}
final OffsetLayer childOffsetLayer = child._layer as OffsetLayer;
childOffsetLayer.offset = offset;
appendLayer(child._layer);
}

isRepaintBoundary为false时,正常遍历子节点绘制。

比如首次绘制第一帧时,从根节点RenderView开始从上到下遍历绘制。

当子节点记录为isRepaintBoundary(即之前已经绘制完成的子节点)时,调用_compositeChild方法。

此时child._needsPaint实际上已经为false。

所以直接调用appendLayer()方法合并子节点所在的Layer图层,添加到当前节点所在Layer的子节点,生成一颗LayerTree,同时触发 markNeedsAddToScene()标脏方法,用于合成阶段的标脏操作,下篇文章会详细介绍。

所以,markNeedsPaint() 从叶子节点向上遍历寻找绘制边界,触发局部绘制。

flushPaint()深度优先逆序遍历,找到绘制边界把当前节点作为祖先节点,从上到下绘制局部树。最后绘制RenderView根节点(如果需要)。

每个isRepaintBoundary为true的RendeObject,都会生成一个新的图层,其所有的子节点都会被绘制在这个新的图层中。

Flutter 使用Layer图层来记录一个层次上所有的RenderObject的绘制过程,每个图层独立刷新,互不影响。

Layer 是上篇文章中提到的AbstractNode的子类,它是实现Tree的基类,RenderObject也是其子类。

Layer和RenderObject存在 1:N的对应关系。

所以,也就是说RepaintBoundary Widget可以控制刷新范围,这也是为什么使用RepaintBoundary Widget可以提高绘制性能的真正原因。

4.2、PaintingContext & Canvas 绘制

具体绘制时,通过PaintingContext获取Canvas,调用Canvas的API接口执行具体的绘制操作,看下Canvas对象的获取逻辑

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
// lib/src/rendering/object.dart PaintingContext
@override
//初始化
Canvas get canvas {
if (_canvas == null)
_startRecording();
return _canvas;
}
//开始绘制
void _startRecording() {
//isRecording的判断标准是canvas是否为空
assert(!_isRecording);
//构造一个PictureLayer对象,绑定绘制区域
_currentLayer = PictureLayer(estimatedBounds);
//构造一个PictureRecorder对象,用于记录绘制指令
_recorder = ui.PictureRecorder();
//构造一个Canvas,依赖PictureRecorder对象
_canvas = Canvas(_recorder);
//将PictureLayer添加到当前图层的LayerTree中
_containerLayer.append(_currentLayer);
}
@protected
@mustCallSuper
//结束绘制
void stopRecordingIfNeeded() {
if (!_isRecording)
return;
assert(() {
//使用debugRepaintRainbowEnabled显示重绘区域
if (debugRepaintRainbowEnabled) {
final Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 6.0
..color = debugCurrentRepaintColor.toColor();
canvas.drawRect(estimatedBounds.deflate(3.0), paint);
}
//使用debugPaintLayerBordersEnabled显示当前Layer边界
if (debugPaintLayerBordersEnabled) {
final Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1.0
..color = const Color(0xFFFF9800);
canvas.drawRect(estimatedBounds, paint);
}
return true;
}());
//绘制指令记录在picture对象
_currentLayer.picture = _recorder.endRecording();
//释放PictureLayer对象
_currentLayer = null;
//释放PictureRecorder对象
_recorder = null;
//释放Canvas对象
_canvas = null;
}

做了几件事情:

  1. 初始化

当Layer图层中的RenderObject要使用Canvas对象进行绘制时,初始化一个PictureLayer对象,添加到当前LayerTree中。

初始化一个PictureRecorder对象,绑定到Canvas对象上。

PicutreRecorder实际上是Flutter Engine中的PictureRecorder对象的代理。

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
//lib/ui/painting/picture_recorder.cc
//构造入口
static void PictureRecorder_constructor(Dart_NativeArguments args) {
UIDartState::ThrowIfUIOperationsProhibited();
DartCallConstructor(&PictureRecorder::Create, args);
}
IMPLEMENT_WRAPPERTYPEINFO(ui, PictureRecorder);
#define FOR_EACH_BINDING(V) \
V(PictureRecorder, isRecording) \
V(PictureRecorder, endRecording)
//注册Native方法
FOR_EACH_BINDING(DART_NATIVE_CALLBACK)
//注册构造方法
void PictureRecorder::RegisterNatives(tonic::DartLibraryNatives* natives) {
natives->Register(
{{"PictureRecorder_constructor", PictureRecorder_constructor, 1, true},
FOR_EACH_BINDING(DART_REGISTER_NATIVE)});
}
//构造方法
fml::RefPtr<PictureRecorder> PictureRecorder::Create() {
return fml::MakeRefCounted<PictureRecorder>();
}
bool PictureRecorder::isRecording() {
return canvas_ && canvas_->IsRecording();
}
//通过SkPictureRecorder构造SkCanvas,picture_recorder_是SkPictureRecorder对象
SkCanvas* PictureRecorder::BeginRecording(SkRect bounds) {
return picture_recorder_.beginRecording(bounds, &rtree_factory_);
}
fml::RefPtr<Picture> PictureRecorder::endRecording(Dart_Handle dart_picture) {
//构造SkPicture对象
fml::RefPtr<Picture> picture =
Picture::Create(dart_picture,
UIDartState::CreateGPUObject(
picture_recorder_.finishRecordingAsPicture()),
canvas_->external_allocation_size());
//清理工作
canvas_->Clear();
canvas_->ClearDartWrapper();
canvas_ = nullptr;
ClearDartWrapper();
return picture;
}

同样的,Dart Framework的Canvas对象实际上是Flutter Engine中Canvas对象的代理。

Flutter Engine中的Canvas:

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
// lib/ui/painting/canvas.cc
static void Canvas_constructor(Dart_NativeArguments args) {
UIDartState::ThrowIfUIOperationsProhibited();
DartCallConstructor(&Canvas::Create, args);
}
IMPLEMENT_WRAPPERTYPEINFO(ui, Canvas);
#define FOR_EACH_BINDING(V) \
V(Canvas, save) \
V(Canvas, saveLayerWithoutBounds) \
V(Canvas, saveLayer) \
V(Canvas, restore) \
V(Canvas, getSaveCount) \
V(Canvas, translate) \
V(Canvas, scale) \
V(Canvas, rotate) \
V(Canvas, skew) \
V(Canvas, transform) \
V(Canvas, clipRect) \
V(Canvas, clipRRect) \
V(Canvas, clipPath) \
V(Canvas, drawColor) \
V(Canvas, drawLine) \
V(Canvas, drawPaint) \
V(Canvas, drawRect) \
V(Canvas, drawRRect) \
V(Canvas, drawDRRect) \
V(Canvas, drawOval) \
V(Canvas, drawCircle) \
V(Canvas, drawArc) \
V(Canvas, drawPath) \
V(Canvas, drawImage) \
V(Canvas, drawImageRect) \
V(Canvas, drawImageNine) \
V(Canvas, drawPicture) \
V(Canvas, drawPoints) \
V(Canvas, drawVertices) \
V(Canvas, drawAtlas) \
V(Canvas, drawShadow)
...
//注册Canvas API方法
FOR_EACH_BINDING(DART_NATIVE_CALLBACK)
//注册Native构造方法
void Canvas::RegisterNatives(tonic::DartLibraryNatives* natives) {
natives->Register({{"Canvas_constructor", Canvas_constructor, 6, true},
FOR_EACH_BINDING(DART_REGISTER_NATIVE)});
}
fml::RefPtr<Canvas> Canvas::Create(PictureRecorder* recorder,
double left,
double top,
double right,
double bottom) {
...
//通过PictureRecoder的begin方法,构造SkCanvas对象
fml::RefPtr<Canvas> canvas = fml::MakeRefCounted<Canvas>(
recorder->BeginRecording(SkRect::MakeLTRB(left, top, right, bottom)));
recorder->set_canvas(canvas);
return canvas;
}
Canvas::Canvas(SkCanvas* canvas) : canvas_(canvas) {}

实际上Flutter Engine中的对象都是Skia引擎的代理对象,最终实现绘制的是Skia引擎中的SkCanvas对象。

所有的Dart Framework层的Canvas绘制操作,都会通过Flutter Engine层的Canvas代理类,最终调用到SkCanvas去实际绘制。

  1. 绘制

当使用Canvas对象绘制时,绘制的指令都会被记录在Flutter Engine 的SkPictureRecorder对象中。

  1. 结束绘制

当结束当前Layer图层的绘制流程时,调用_recorder.endRecording()获取一个SkPicture对象,SkPicture对象包含了所有的绘制指令,并写入PictureLayer中。

至此所有的Layer图层绘制完成,形成一颗含有所有绘制操作记录的LayerTree。

并且,在当前LayerTree中,每个用到Canvas绘制的Layer图层的同层级中,总是有一个或多个PictureLayer,用来记录绘制信息。

1
2
3
4
5
// lib/src/rendering/layer.dart PictureLayer
set picture(ui.Picture picture) {
markNeedsAddToScene();
_picture = picture;
}

最后调用markNeedsAddToScene()标记该Layer的_needsAddToScene为true,为接下来的renderView.compositeFrame()做准备。

4.3、_needsCompositing 的作用

在markNeedsCompositingBitsUpdate()标记阶段,记录了RenderObject的_needsCompositing是否需要合成的标志位。

其原理是,当一个RenderObject节点是isRepaintBoundary || alwaysNeedsCompositing,那么它及其所有的祖先节点都会被标记为_needsCompositing。

这个标志位的作用在PaintingContext中的pushXXX特殊绘制相关方法中会用到,用于标识是否需要新建一个Layer图层来实现一些特定的图形效果,比如裁剪,变换等。

以pushClipRect裁剪方法为例:

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
// lib/src/rendering/object.dart PaintingContext
ClipRectLayer pushClipRect(bool needsCompositing, Offset offset, Rect clipRect, PaintingContextCallback painter, { Clip clipBehavior = Clip.hardEdge, ClipRectLayer oldLayer }) {
final Rect offsetClipRect = clipRect.shift(offset);
//需要合成
if (needsCompositing) {
//新建一个ClipRectLayer图层,会被重复使用
final ClipRectLayer layer = oldLayer ?? ClipRectLayer();
//Layer裁剪范围和裁剪信息
layer
..clipRect = offsetClipRect
..clipBehavior = clipBehavior;
//将新建的Layer 添加到LayerTree,并在其上进行绘制
pushLayer(layer, painter, offset, childPaintBounds: offsetClipRect);
return layer;
} else {
//直接在原有Canvas上裁剪、绘制
clipRectAndPaint(offsetClipRect, clipBehavior, offsetClipRect, () => painter(this, offset));
return null;
}
}
void pushLayer(ContainerLayer childLayer, PaintingContextCallback painter, Offset offset, { Rect childPaintBounds }) {
//图层如果被复用,清空
if (childLayer.hasChildren) {
childLayer.removeAllChildren();
}
//结束当前PaintingContext绘制,记录到PictureLayer中的SkPicture
stopRecordingIfNeeded();
//将新Layer添加到LLayerTree appendLayer(childLayer);
//为childLayer新建PaintingContext,持有独立Canvas
final PaintingContext childContext = createChildContext(childLayer, childPaintBounds ?? estimatedBounds);
//开始绘制
painter(childContext, offset);
//绘制完成,childLayer绘制操作记录到PictureLayer中的SkPicture
childContext.stopRecordingIfNeeded();
}

如果被标记为合成,那么就新建一个Layer,设置裁剪信息,并绑定到新的PaintingContext上,持有独立的Canvas,进行特殊效果绘制。

使用方是RenderClipRect.paint()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// lib/src/rendering/proxy_box.dart RenderClipRect
class RenderClipRect extends _RenderCustomClip<Rect> {
/// Creates a rectangular clip.
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
_updateClip();
//调用pushClipRect方法
layer = context.pushClipRect(
needsCompositing,//在markNeedsCompositingBitsUpdate被标记
offset,
_clip,
super.paint,
clipBehavior: clipBehavior,
oldLayer: layer as ClipRectLayer,
);
} else {
layer = null;
}
}

思考一下为什么子节点在独立图层上绘制,这些特殊绘制操作也需要在独立图层上绘制?

先看下不独立绘制,直接在原有Canvas上裁剪、绘制的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// lib/src/rendering/clip.dart ClipContext
void clipRectAndPaint(Rect rect, Clip clipBehavior, Rect bounds, void painter()) {
_clipAndPaint((bool doAntiAias) => canvas.clipRect(rect, doAntiAlias: doAntiAias), clipBehavior, bounds, painter);
}
void _clipAndPaint(void canvasClipCall(bool doAntiAlias), Clip clipBehavior, Rect bounds, void painter()) {
...
case hardEdge:
//保存状态
canvas.save();
//裁剪操作
canvasClipCall(false);
break;
case Clip.antiAliasWithSaveLayer:
canvasClipCall(true);
canvas.saveLayer(bounds, Paint());
break;
...
//绘制
painter();
//恢复状态
canvas.restore();
}

这里就明白了,在子节点没有独立绘制情况下,裁剪操作需要做到在不影响子节点绘制的情况下,借助canvas的save()restore()方法来实现。

并且在一些场景下会触发saveLayer,也就是离屏渲染,这个操作对性能影响巨大

而在子节点在独立图层绘制的情况下,特殊效果绘制也就在新建的独立图层上绘制就好了,不用再save()restore()了,更不用saveLayer了,对性能更友好。

并且由于裁剪等特殊绘制在独立图层的存在,可以把绘制范围切分的更细粒度,而在Flutter Engine里对于图层是有缓存的,也可以提高绘制性能。

4.4、Layer 种类

Layer 大体上分为两种类型,ContainerLayer 和非ContainerLayer:

  • 非ContainerLayer,用于绘制,一般为LayerTree每一层的尾节点,也有可能在中间节点,比如stopRecording后,再appendLayer
    • PictureLayer,用于记录一般绘制操作,大部分RenderObject都是绘制在这上面
    • TextureLayer,主要用于外接纹理绘制,对应的RenderObject是TextureBox,Widget 是 Texture
    • PlatformViewLayer,用于嵌入平台 (Android、iOS) 纹理绘制,对应的RenderObject是PlatformViewRenderBox,Widget 是 PlatformViewSurface
  • ContainerLayer,本身不具备绘制能力,一般用于添加非ContainerLayer,形成LayerTree
    • ClipRectLayer、ClipRRectLayer、ClipPathLayer,裁剪层,可以指定裁剪和矩形行为参数。共有4种裁剪行为,none、hardEdge、antiAlias、antiAliashWithSaveLayer(会触发SaveLayer)
    • OffsetLayer,偏移层,可以指定坐标偏移量
    • TransformLayer,变换图层,可以指定变换矩阵参数
    • OpacityLayer,透明层,可以指定透明度
    • PhysicalModelLayer,透明层,可以指定透明度
    • ColorFilterLayer,颜色过滤层,可以指定颜色和混合模式参数
    • BackdropFilterLayer:背景过滤层,可以指定背景图参数

5、总结

本文主要分析了合成标记、paint绘制、LayerTree等相关内容,下篇文章继续分析具体的合成阶段。

本文链接: http://w4lle.com/2021/02/01/flutter-ui-paint/

版权声明:本文为 w4lle 原创文章,可以随意转载,但必须在明确位置注明出处!
本文链接: http://w4lle.com/2021/02/01/flutter-ui-paint/