上篇我们讲了RenderObject
中layout
和paint
流程。layout
最终计算出来的布局信息是提供给paint
使用的,paint
的flush
阶段会调用到自身的void paint(PaintingContext context, Offset offset)
方法,我们来回顾下这个方法。
/// code 4.1
///[RenderClipOval] 中的实现
void paint(PaintingContext context, Offset offset) {
if (child != null) {
if (clipBehavior != Clip.none) {
_updateClip();
layer = context.pushClipPath(
needsCompositing,
offset,
_clip,
_getClipPath(_clip!),
super.paint,
clipBehavior: clipBehavior,
oldLayer: layer as ClipPathLayer?,
);
} else {
context.paintChild(child!, offset);
layer = null;
}
} else {
layer = null;
}
}
这里引入了两个重要的对象,一个是Layer
,一个是PaintingContext
。_clip
是一个 Rect
对象,context.pushClipPath
通过_clip
做layer
的裁剪,得到的新图层重新赋值给layer
。先看一下RenderObject
中关于layer
的定义。
RenderObject 中的 Layer
/// code 4.2
/// [RenderObejct] 中的代码
@protected
ContainerLayer? get layer {
assert(!isRepaintBoundary || _layerHandle.layer == null || _layerHandle.layer is OffsetLayer);
return _layerHandle.layer;
}
@protected
set layer(ContainerLayer? newLayer) {
assert(
!isRepaintBoundary,
'Attempted to set a layer to a repaint boundary render object.\n'
'The framework creates and assigns an OffsetLayer to a repaint '
'boundary automatically.',
);
_layerHandle.layer = newLayer;
}
final LayerHandle<ContainerLayer> _layerHandle = LayerHandle<ContainerLayer>();
对layer
的操作都会转到_layerHandle
上来,LayerHandle
的接口比较简单,看下面set layer(T? layer)
方法的实现,它只是持有layer
并对layer
做引用计数而已,也就是这个layer
被多少个LayerHandle
持有了,当持有数为 0 时,_layer?_unref()
里就会对layer
做资源释放工作。
/// code 4.3
class LayerHandle<T extends Layer> {
/// Create a new layer handle, optionally referencing a [Layer].
LayerHandle([this._layer]) {
if (_layer != null) {
_layer!._refCount += 1;
}
}
T? _layer;
/// The [Layer] whose resources this object keeps alive.
///
/// Setting a new value or null will dispose the previously held layer if
/// there are no other open handles to that layer.
T? get layer => _layer;
set layer(T? layer) {
assert(
layer?.debugDisposed != true,
'Attempted to create a handle to an already disposed layer: $layer.',
);
if (identical(layer, _layer)) {
return;
}
_layer?._unref();
_layer = layer;
if (_layer != null) {
_layer!._refCount += 1;
}
}
@override
String toString() => 'LayerHandle(${_layer != null ? _layer.toString() : 'DISPOSED'})';
}
对Layer
先暂时了解这么多,再看PaintingContext
。
PaintingContext
/// code 4.4
class PaintingContext extends ClipContext {
/// Creates a painting context.
///
/// Typically only called by [PaintingContext.repaintCompositedChild]
/// and [pushLayer].
@protected
PaintingContext(this._containerLayer, this.estimatedBounds);
final ContainerLayer _containerLayer;
/// An estimate of the bounds within which the painting context's [canvas]
/// will record painting commands. This can be useful for debugging.
///
/// The canvas will allow painting outside these bounds.
///
/// The [estimatedBounds] rectangle is in the [canvas] coordinate system.
final Rect estimatedBounds;
...
// 一系列 layer 操作
ClipRectLayer? pushClipRect(...) ...
ClipRRectLayer? pushClipRRect(...) ...
ClipPathLayer? pushClipPath(...) ...
ColorFilterLayer pushColorFilter(...) ...
TransformLayer? pushTransform(...) ...
OpacityLayer pushOpacity(...) ...
...
}
PaintingContext
是绘制上下文,包含一系列 layer 操作,构造函数接受两个参数,一个是_containerLayer
,一个是estimatedBounds
。estimatedBounds
限制了图层的位置和大小,ContainerLayer
是Layer
的一种,从名字就猜到它是用来组合Layer
的。根据code 4.1
中的代码,看一下pushClipPath
的实现
/// code 4.5
ClipPathLayer? pushClipPath(bool needsCompositing, Offset offset, Rect bounds, Path clipPath, PaintingContextCallback painter, { Clip clipBehavior = Clip.antiAlias, ClipPathLayer? oldLayer }) {
if (clipBehavior == Clip.none) {
painter(this, offset);
return null;
}
final Rect offsetBounds = bounds.shift(offset);
final Path offsetClipPath = clipPath.shift(offset);
// 这里我们着重看这条件分支里面的处理
if (needsCompositing) {
final ClipPathLayer layer = oldLayer ?? ClipPathLayer();
layer
..clipPath = offsetClipPath
..clipBehavior = clipBehavior;
// 构造一个新的 layer,push 到 _containerLayer 上
pushLayer(layer, painter, offset, childPaintBounds: offsetBounds);
return layer;
} else {
// 通过 canvas 操作将裁剪操作保存到 _currentLayer 上
clipPathAndPaint(offsetClipPath, clipBehavior, offsetBounds, () => painter(this, offset));
return null;
}
}
// super.paint 的实现
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
context.paintChild(child!, offset);
}
}
先明确一下,这个方法里面有一个方法传参painter
,根据 code 4.1 中的代码,就是super.paint
,看它的实现调用[PaintingContext].paintChild
的操作,其实就是绘制子节点,无论是pushLayer
,还是clipPathAndPaint
,都将这个painter
操作传进去了。
pushLayer
/// code 4.6
void pushLayer(ContainerLayer childLayer, PaintingContextCallback painter, Offset offset, { Rect? childPaintBounds }) {
...
appendLayer(childLayer);
// createContext 实现如下面的代码,创建一个新的绘制上下文,此时的 childLayer 是上面传的 ClipPathLayer
final PaintingContext childContext = createChildContext(childLayer, childPaintBounds ?? estimatedBounds);
// 执行 child 的绘制
painter(childContext, offset);
...
}
@protected
void appendLayer(Layer layer) {
...
// 移除 layer 原先的 parent(如果存在)
layer.remove();
// 加到 _containerLayer
_containerLayer.append(layer);
}
@protected
PaintingContext createChildContext(ContainerLayer childLayer, Rect bounds) {
return PaintingContext(childLayer, bounds);
childLayer
是code 4.5
中传进来的ClipPathLayer
,已经限制了裁剪区域。pushLayer
在把childLayer
添加到_containerLayer
之后,同时将它传递到childContext
去绘制,这样就限制了child
绘制和区域,实现裁剪的功能。
clipPathAndRepaint
/// code 4.7
void _clipAndPaint(void Function(bool doAntiAlias) canvasClipCall, Clip clipBehavior, Rect bounds, VoidCallback painter) {
// canvas 操作
canvas.save();
switch (clipBehavior) {
case Clip.none:
break;
case Clip.hardEdge:
canvasClipCall(false);
case Clip.antiAlias:
canvasClipCall(true);
case Clip.antiAliasWithSaveLayer:
canvasClipCall(true);
canvas.saveLayer(bounds, Paint());
}
// painter 实际上就是 childContext.paint
painter();
if (clipBehavior == Clip.antiAliasWithSaveLayer) {
canvas.restore();
}
canvas.restore();
}
void clipPathAndPaint(Path path, Clip clipBehavior, Rect bounds, VoidCallback painter) {
// 执行 canvas 的裁剪操作
_clipAndPaint((bool doAntiAlias) => canvas.clipPath(path, doAntiAlias: doAntiAlias), clipBehavior, bounds, painter);
}
根据 clipBehavior,canvas 先做了一个clipPath
的裁剪操作,然后再绘制 child。canvas 操作最终是会反映到_currentLayer
上的,看下面这段代码:
/// code 4.8
bool get _isRecording {
// 开始记录时,_canvas会被初始化
final bool hasCanvas = _canvas != null;
...
return hasCanvas;
}
// Recording state
PictureLayer? _currentLayer;
ui.PictureRecorder? _recorder;
Canvas? _canvas;
@override
Canvas get canvas {
if (_canvas == null) {
_startRecording();
}
assert(_currentLayer != null);
return _canvas!;
}
void _startRecording() {
assert(!_isRecording);
_currentLayer = PictureLayer(estimatedBounds);
_recorder = ui.PictureRecorder();
_canvas = Canvas(_recorder!);
_containerLayer.append(_currentLayer!);
}
@protected
@mustCallSuper
void stopRecordingIfNeeded() {
if (!_isRecording) {
return;
}
...
_currentLayer!.picture = _recorder!.endRecording();
_currentLayer = null;
_recorder = null;
_canvas = null;
}
先看_startRecording
方法,开始记录绘制时,_canvas
会被初始化,同时会传入_recorder
用来记录任务在_canvas
上执行的操作,最终在stopRecordingIfNeeded
时,通过recorder!.endRecording()
将之前记录的绘制动作输出成一个picture
,然后赋值给_currentLayer!.picture
,注意_currentLayer
是在_startRecording
时就已经append
到_containerLayer
上的。
无论是pushLayer
还是clipPathAndRepaint
,其绘制结果都存在于_containerLayer
上,同时这个_containerLayer
是在 PaintingContext 实例化时由外部提供。还记得我们 code 4.5 中说到的绘制子节点的方法painter
,它最终会走到[PaintContext].paintChild
这里
/// code 4.9
void paintChild(RenderObject child, Offset offset) {
...
if (child.isRepaintBoundary) {
stopRecordingIfNeeded();
// 这里会生成一个新的 layer,用来绘制 child
// _compositeChild 方法也会根据 _needPaint 这个标记去判断是不是要重新生成
_compositeChild(child, offset);
} else if (child._wasRepaintBoundary) {
child._layerHandle.layer = null;
child._paintWithContext(this, offset);
} else {
child._paintWithContext(this, offset);
}
}
如果是绘制边界,那么新建一个PaintContext
去绘制;否则将其绘制到当前的PaintingContext
上。
从上面的绘制操作可以看出,每一个节点在绘制时,都要绘制子节点;根据isRepaintBoundary
判断要不要生成一个独立的绘制区域,再根据_needsPaint
判断是不是要新建一个PaintingContext
,这样就能保证每次绘制时只绘制脏区域。
另外,PaintContext
里面针对 child layer的处理完成后,最终都会走到 code 4.6 中的appendLayer
方法里,这个方法会走到_containerLayer.append
,形成Layer Tree
。
那么顶层layer
是哪里来的?这就回归到RenderObject
中关于layer
的传递了,RenderObject
持有着一个LayerHandle
,而LayerHandle
里layer
属性默认是null
的。上篇我们讲过RenderObject
的继承关系,根RenderObject
是RenderView
,通过追踪一下RenderView
的实例化,就能找到在RenderView._updateMatricesAndCreateNewRootLayer
时生成的rootLayer
。
/// code 4.10
void prepareInitialFrame() {
...
scheduleInitialLayout();
scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
...
}
void scheduleInitialPaint(ContainerLayer rootLayer) {
...
// 设置 layer
_layerHandle.layer = rootLayer;
...
owner!._nodesNeedingPaint.add(this);
}
Matrix4? _rootTransform;
TransformLayer _updateMatricesAndCreateNewRootLayer() {
_rootTransform = configuration.toMatrix();
final TransformLayer rootLayer = TransformLayer(transform: _rootTransform);
rootLayer.attach(this);
...
return rootLayer;
}
root RenderObject 是RenderView
,root Layer 是TransformLayer
。rootLayer.attach
到当前RenderView
上时,也会将RenderView
传递到所有child layer
上,也就是所有layer
的owner
都会是同一个RenderView
。
现在我们能拿到rootLayer
了,只是rootLayer
怎么去绘制到界面上还不清楚。实际上对于layer
这个对象我们目前还一知半解,只知道它里面可能保存着绘制信息。跟前几篇一样,还是从对象的关键属性和继承关系着手看
Layer
/// code 4.11
// 1. 如 code 4.3 中提到的,主要是用来配合 LayerHandle 做引用计数和资源释放
int _refCount = 0;
void _unref(){};
// 2. 继承自 AbstractNode 的接口,构成树形结构。从 parent 的返回可以断定每一个节点都是或继承自ContainerLayer
ContainerLayer? get parent => super.parent as ContainerLayer?;
void dropChild(Layer child){};
void adoptChild(Layer child){};
// 3. 兄弟节点双向链接结构,在 append 时会处理这个兄弟节点的关系
Layer? get nextSibling => _nextSibling;
Layer? _nextSibling;
Layer? get previousSibling => _previousSibling;
Layer? _previousSibling;
// 前面提到的节点的关系的形成就在这个方法里面处理
void append(Layer child){};
// 4. 标记这个 Layer 是否已经更新过,标志的节点会在随后走到 addToScene 方法
bool _needsAddToScene = true;
void markNeedsAddToScene() {};
bool get alwaysNeedsAddToScene => false;
// 5. addToScene 是将当前 Layer 中绘制信息通过 builder 推送到 Engine 上绘制
// 生成的绘制信息保存到 _engineLayer 上。
void addToScene(ui.SceneBuilder builder);
vid _addToSceneWithRetainedRendering(ui.SceneBuilder builder){};
// 6. 如 5 中提到的,_engineLayer 是当前 layer 在 Engine 中的映射对象。
ui.EngineLayer? get engineLayer => _engineLayer;
set engineLayer(ui.EngineLayer? value) {};
ui.EngineLayer? _engineLayer;
这段代码比较长,但总的来看 1、2、3 点都是为了形成Layer Tree
的;4、5、6 点是跟绘制有关的,_enginelayer
就是这个Layer
的绘制结果,它是 Layer 在 Engine 中的映射。在它的子类上,会保存着一些绘制的信息,比如下面这段在ClipRectLayer
中的代码
// code 4.11
void _addToSceneWithRetainedRendering(ui.SceneBuilder builder) {
...
if (!_needsAddToScene && _engineLayer != null) {
// 节点有更新时会走 addRetained,就是会把上次的 _engineLayer 推送到 Engine 中渲染
builder.addRetained(_engineLayer!);
return;
}
// 否则从新生成 _engineLayer,看以下的实现
addToScene(builder);
...
_needsAddToScene = false;
}
// Layer 中的 addToScene 是空实现,看子类 ClipRectLayer 的
void addToScene(ui.SceneBuilder builder) {
...
bool enabled = true;
...
if (enabled) {
engineLayer = builder.pushClipRect(
clipRect!,
clipBehavior: clipBehavior,
oldLayer: _engineLayer as ui.ClipRectEngineLayer?,
);
} else {
engineLayer = null;
}
// 绘制子节点,这里是一个递归处理,会更新底下所有的子孙节点的 Layer
addChildrenToScene(builder);
if (enabled) {
builder.pop();
}
}
对于ClipRectLayer
来说,它的绘制信息就是clipRect
和clipBehavior
,然后通过ui.SceneBuilder
中的pushClipRect
调用Engine
中相应的操作方法生成EngineLayer
并保存下来。ui.SceneBuilder
和ui.EngineLayer
都是Engine
对象。
每一个Layer
的addToScene
都会触发底下所有子孙节点的更新,子孙节点或是命中缓存处理builder.addRetained
,或是走重新添加addToScene
。不管哪种方式,最终都是利用ui.SceneBuilder
去跟Engine
层做交互,ui.SceneBulider
实例化在RenderView
中。
// code 4.12
// [RenderView] 中的方法
void compositeFrame() {
...
try {
final ui.SceneBuilder builder = ui.SceneBuilder();
final ui.Scene scene = layer!.buildScene(builder);
if (automaticSystemUiAdjustment) {
_updateSystemChrome();
}
// _view 是 ui.FlutterView,用来绘制界面的而已。render 也对应着 Engine 中的方法。
_view.render(scene);
scene.dispose();
...
}
...
}
// ContainerLayer 中的 buildScene 方法
ui.Scene buildScene(ui.SceneBuilder builder) {
updateSubtreeNeedsAddToScene();
// 这里即是更新 layer tree,builder 会保存有 layer tree 的信息
addToScene(builder);
...
_needsAddToScene = false;
// 生成 ui.Scene,返回给 _view 最终渲染到界面上
final ui.Scene scene = builder.build();
return scene;
}
ui.SceneBuilder
会根据layer tree
的信息生成ui.Scene
,通过ui.FlutterView
最终将ui.Scene
渲染到界面上。渲染方法是_view.render
,它接受一个ui.Scene
对象,而ui.Scene
是通过ui.SceneBuilder
根据layer tree
生成的。
总结
本文讲了跟Paint
流程有关的几个重要的概念:Layer:存储着绘制信息的对象,同时持有着 Engine 层的绘制结果 EngineLayer;PaintingContext:绘制上下文,处理 Layer 的操作,构建Layer Tree
,同时最后也讲了关于形成的Layer Tree
最终是怎么通知到Engine
中进行绘制的。
到现在,Widget
、Element
、RenderObject
、Layer
,包括Layout
、Paint
流程相关的东西都讲完了,最后一篇文章会根据前面积累的这些知识点,重头梳理一下整个渲染流程,从首帧渲染到我们日常开发中比较关注的部分,做一个完整性补充。
网友评论