ParentData 和各种 Mixin 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 @startuml BoxParentData --|> ParentData ContainerParentDataMixin --|> ParentData ContainerBoxParentData --|> BoxParentData ContainerBoxParentData --|> ContainerParentDataMixin RenderObject *-- ParentData RenderObjectWithChildMixin --|> RenderObject ContainerRenderObjectMixin --|> RenderObject class ContainerParentDataMixin{ + previousSlibling + nextSibling } class ContainerRenderObjectMixin { + firstChild *-- ContainerParentDataMixin + lastChild + childCount } class BoxParentData{ + offset } class RenderObject{ + parentData } @enduml
ParentData 在 Flutter 的布局系统中,该类负责存储父节点所需要的子节点的布局信息
BoxParentData – offset 包含有一个 offset 属性,该属性用于存储 child 的布局信息,也就是 child 应该被摆在哪个位置。与 Android 不同,Flutter并不是以父View为坐标基础进行绘制的,所以需要带上 offset 参数,得到一个累加的偏移值。至于偏移的参考点在哪里,可以看下面的绘制阶段的原理解析。
ContainerParentDataMixin – previousSibling – nextSibling 该类使用频率很高,基本上所有父节点的 ParentData 都混入了该类,该类需要与ContainerRenderObjectMixin 共同使用,主要解决了对 child 的管理,previousSibling nextSibling 都是child RenderObject, 从而组成把父节点的所有 child 组成了一个双链表
ContainerBoxParentData 空类,继承了 BoxParentData 并且混入了 ContainerParentDataMixin, 使其拥有二者的能力
RenderObject Flutter 中真正实现布局和绘制的类
RenderObjectWithChildMixin 实现对只有一个child的管理
ContainerRenderObjectMixin – firstChild – lastChild – childCount 实现对多child的管理,firstChild、lastChild、childCount,用来获取首末 child,child的个数,配合使用 ContainerParentDataMixin 中的 previousSibling、nextSibling就可以对 child 进行遍历了。 child 的insert、remove、move等操作都在这个Mixin中实现
除了以上的类,Flutter 还提供了 RenderBoxContainerDefaultsMixin,该类提供了一些 RenderBox 默认的行为方法,如上面绘制 child 的流程调用该类中的 defaultPaint(PaintingContext context, Offset offset) 就可以了,可以简化一些模板代码。
layout 在 RenderBox 中,控件大小的值为 _size 成员,它只包含宽高两个属性值,我们可以通过该成员的 set 和 get 方法访问或修改它的值。在测量时( layout 方法中),parent 会传给当前 RenderBox 一个大小的限制,为 BoxConstraints 类型,最后测量得到的 size 必须满足这个限制,在 Flutter 的 debug 模式下对 size 是否满足 constraints 做了 assert 检查,如果检查未通过就会布局失败。所以测量上我们要做的是下面两点:
如果没有 child,那么根据自身的属性计算出满足 constraints 的 size.
如果有 child,那么综合自身的属性和 child 的测量结果计算出满足 constraints 的 size.
测量和布局都在layout方法中完成
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 void layout(Constraints constraints, { bool parentUsesSize = false }) { RenderObject relayoutBoundary; if (!parentUsesSize || sizedByParent || constraints.isTight || parent is ! RenderObject) { relayoutBoundary = this ; } else { final RenderObject parent = this .parent; relayoutBoundary = parent._relayoutBoundary; } if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) { return ; } _constraints = constraints; if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) { visitChildren((RenderObject child) { child._cleanRelayoutBoundary(); }); } _relayoutBoundary = relayoutBoundary; if (sizedByParent) { try { performResize(); } catch (e, stack) { _debugReportException('performResize' , e, stack); } } try { performLayout(); markNeedsSemanticsUpdate(); } catch (e, stack) { _debugReportException('performLayout' , e, stack); } _needsLayout = false ; markNeedsPaint(); }
parentUsesSize layout 方法中由父节点传入,表示父节点的 size 依赖当前节点的 size
constraints layout 方法中从父节点传入,当前节点会从更新到自己的变量中保存
relayoutBoundary relayoutBoundary 是framework层自动设置的,如果满足 (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject)
则该节点是一个 relayoutBoundary, 否则会从父节点寻找。
sizedByParent & performResize() 这两个必须成对配置,performResize() 方法如果被重写,sizedByParent 也必须重写为true,并且你不应该在 perfromLayout() 方法中对 size 重新赋值。 sizedByParent 意为该控件的大小是否能仅通过 parent 赋予它的 constraints 就可以被确定下来了,即该控件的大小与它自身的属性和与它的 child 都无关,比如如果一个控件永远充满 parent 的大小,那么 sizedByParent 就应该返回 true。 这个操作不是必须的 官方文档中说这是一个优化项,如果完全不care这个,sizedByParent = false,那始终是正确的。 我的理解是,把 size 的计算单独抽出,避免每次 relayout 时对 size 重新计算。 performResize() 这个方法名字可能会造成误会,认为测量必须放在这里,其实大多是情况下,测量和布局都在 performLayout() 中
performLayout() 上面已经说得差不多了
如果是在标脏后,在一帧绘制的回调中刷新布局,不会调用layout方法,而是调用_layoutWithoutSize
1 2 3 4 5 6 7 8 9 10 void _layoutWithoutResize() { try { performLayout(); markNeedsSemanticsUpdate(); } catch (e, stack) { _debugReportException('performLayout' , e, stack); } _needsLayout = false ; markNeedsPaint(); }
paint 父节点绘制子节点的入口
paintContext.paintChild()
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 void paintChild(RenderObject child, Offset offset) { if (child.isRepaintBoundary) { stopRecordingIfNeeded(); _compositeChild(child, offset); } else { child._paintWithContext(this , offset); } } void _compositeChild(RenderObject child, Offset offset) { if (child._needsPaint) { repaintCompositedChild(child, debugAlsoPaintedParent: true ); } else { } final OffsetLayer childOffsetLayer = child._layer; childOffsetLayer.offset = offset; appendLayer(child._layer); } static void _repaintCompositedChild( RenderObject child, { bool debugAlsoPaintedParent = false , PaintingContext childContext, }) { OffsetLayer childLayer = child._layer; if (childLayer == null ) { child._layer = childLayer = OffsetLayer(); } else { childLayer.removeAllChildren(); } childContext ??= PaintingContext(child._layer, child.paintBounds); child._paintWithContext(childContext, Offset.zero); childContext.stopRecordingIfNeeded(); } @protected void appendLayer(Layer layer) { layer.remove(); _containerLayer.append(layer); }
在 paintChild 方法中,若 child 是一个 repaintBoundary ,会为 child 创建一个 layer ,然后再调用 child._paintWithContext(this, offset) ;如果 isRepaintBoundary 为false,则直接调用 child._paintWithContext(this, offset)。。 所以这个layer又是怎样???
在drawFrame()中,paint 标脏节点刷新时会直接执行 PaintContext.repaintCompositeChild()
_paintWithContext(..) & paint()
1 2 3 4 5 6 7 8 9 10 11 12 13 void _paintWithContext(PaintingContext context, Offset offset) { if (_needsLayout) return ; RenderObject debugLastActivePaint; _needsPaint = false ; try { paint(context, offset); } catch (e, stack) { _debugReportException('paint' , e, stack); } } void paint(PaintingContext context, Offset offset) { }
RenderObject 中的 paint 是一个空方法,交给子类来实现。 以 RenderFlex 为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @override void paint(PaintingContext context, Offset offset) { if (!_hasOverflow) { defaultPaint(context, offset); return ; } ... } void defaultPaint(PaintingContext context, Offset offset) { ChildType child = firstChild; while (child != null ) { final ParentDataType childParentData = child.parentData; context.paintChild(child, childParentData.offset + offset); child = childParentData.nextSibling; } }
因为Flex没有要绘制的内容,所以调用 RenderBoxContainerDefaultsMixin 提供的默认实现。在 defaultPaint 中遍历child,绘制child时将 childParentData.offset累加到offset中。所以,这个 offset 的基准到底是个啥????
PaintingContext 和 Layer drawFrame() PaintContext 可以类比于 BuildContext,Layer 和 Element 最终会组成一个 Layer树,最终渲染是在C++的engine层完成的。在flutter的framework层构成了一颗Layer树,传送到engine进行绘制。
回过头来看,在 drawFrame() 中对需要paint的节点是如何处理的。
1 2 3 4 5 6 7 8 9 10 void drawFrame() { Boost.resetIdleCallbacks(); assert (renderView != null ); pipelineOwner.flushLayout(); pipelineOwner.flushCompositingBits(); pipelineOwner.flushPaint(); renderView.compositeFrame(); pipelineOwner.flushSemantics(); }
先直接来看 pipelineOwner.flushPaint(); 和里面调用到的 PaintContext._repaintCompositedChild
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 ) { if (node._layer.attached) { PaintingContext.repaintCompositedChild(node); } else { node._skippedPaintingOnLayer(); } } } } finally { } }
PaintingContext._repaintCompositedChild(…)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static void _repaintCompositedChild( RenderObject child, { bool debugAlsoPaintedParent = false , PaintingContext childContext, }) { assert (child.isRepaintBoundary); OffsetLayer childLayer = child._layer; if (childLayer == null ) { child._layer = childLayer = OffsetLayer(); } else { childLayer.removeAllChildren(); } childContext ??= PaintingContext(child._layer, child.paintBounds); child._paintWithContext(childContext, Offset.zero); childContext.stopRecordingIfNeeded(); }
再次回顾 PaintingContext._repaintCompositedChild 方法,我们发现,它为 RepaintBoundary ,创建了PaintingContext 和 layer。 说明 RepaintBoundary 、PaintingContext 、layer 是一一对应的, 即 layer 是绘制的基本单位,只有 RepaintBoundary 极其子 RenderObject 共享一个layer, 每次绘制会创建都会为 layer 创建一个 PaintingContext 。而 paint 过程中的offset的参考点,就是layer的左上角的坐标原点。
renderView.compositeFrame();
1 2 3 4 5 6 7 8 9 10 11 12 13 void compositeFrame() { Timeline.startSync('Compositing' , arguments: timelineWhitelistArguments); try { final ui.SceneBuilder builder = ui.SceneBuilder(); final ui.Scene scene = layer.buildScene(builder); if (automaticSystemUiAdjustment) _updateSystemChrome(); _window.render(scene); scene.dispose(); } finally { Timeline.finishSync(); } }
renderView 是页面的根RenderObject,renderView.layer 是页面的根layer。 调用 layer.buildScene(builder);
生成了一个scene, 调用_window.render(scene) 发送给engine层进行渲染
Layer的标脏和局部刷新 layer.buildScene
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 ui.Scene buildScene(ui.SceneBuilder builder) { List <PictureLayer> temporaryLayers; updateSubtreeNeedsAddToScene(); addToScene(builder); _needsAddToScene = false ; final ui.Scene scene = builder.build(); return scene; } @override void updateSubtreeNeedsAddToScene() { super .updateSubtreeNeedsAddToScene(); Layer child = firstChild; while (child != null ) { child.updateSubtreeNeedsAddToScene(); _needsAddToScene = _needsAddToScene || child._needsAddToScene; child = child.nextSibling; } } @override void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) { addChildrenToScene(builder, layerOffset); } void addChildrenToScene(ui.SceneBuilder builder, [ Offset childOffset = Offset.zero ]) { Layer child = firstChild; while (child != null ) { if (childOffset == Offset.zero) { child._addToSceneWithRetainedRendering(builder); } else { child.addToScene(builder, childOffset); } child = child.nextSibling; } }
renderView 的 layer 是一个 OffsetLayer ,继承自 ContainerLayer 。 Layer.buildScene(…) 这个方法在framework调用的地方很少,可以认为只有 renderView 的 layer 会调用 buildScene 方法创建创建一个 scene , 即 scene 包含了页面的完整 layer 树。
首先会调用 updateSubtreeNeedsAddToScene , 需要update的原因是, 如果子layer标脏, 那显然父layer 也需要标脏
_addToSceneWithRetainedRendering
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void markNeedsAddToScene() { if (_needsAddToScene) { return ; } _needsAddToScene = true ; } void _addToSceneWithRetainedRendering(ui.SceneBuilder builder) { if (!_needsAddToScene && _engineLayer != null ) { builder.addRetained(_engineLayer); return ; } addToScene(builder); _needsAddToScene = false ; }
markNeedAddToScene() 对 layer 节点进行标脏。在layer属性发生改变时会进行标脏;当子layer添加到父layer时也会进行标脏。
这个方法是唯一用到使用到 _needsAddToScene 标脏flag值的地方。 如果 _needsAddToScene 为false ,调用 builder.addRetained(_engineLayer); 这个 _engineLayer
_eingineLayer 是 layer.addToScene(…) 返回的engine生成的图层(ContainerLayer例外,没有engineLayer)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @override void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) { bool enabled = firstChild != null ; if (enabled) engineLayer = builder.pushOpacity(alpha, offset: offset + layerOffset, oldLayer: _engineLayer); else engineLayer = null ; addChildrenToScene(builder); if (enabled) builder.pop(); }
SceneBuilder.addRetained()
1 2 3 4 5 6 void addRetained(EngineLayer retainedLayer) { final _EngineLayerWrapper wrapper = retainedLayer; _addRetained(wrapper._nativeLayer); } void _addRetained(EngineLayer retainedLayer) native 'SceneBuilder_addRetained' ;
addRetained 直接调用了engine层的代码,对layer进行复用。。。
直接使用 layer 进行UI绘制 flutter最终是绘制在layer上的,那样的话,实际上,可以脱离 Widget、Element、RenderObject ,直接操作 layer 进行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 import 'dart:ui' ;import 'dart:math' ;import 'package:flutter/material.dart' ;import 'package:flutter/rendering.dart' ;void main(){ final OffsetLayer rootLayer = new OffsetLayer(); final PictureLayer pictureLayer = new PictureLayer(Rect.zero); rootLayer.append(pictureLayer); PictureRecorder recorder = PictureRecorder(); Canvas canvas = Canvas(recorder); Paint paint = Paint(); paint.color = Colors.primaries[Random().nextInt(Colors.primaries.length)]; canvas.drawRect(Rect.fromLTWH(0 , 0 , 300 , 300 ), paint); pictureLayer.picture = recorder.endRecording(); SceneBuilder sceneBuilder = SceneBuilder(); rootLayer.addToScene(sceneBuilder); Scene scene = sceneBuilder.build(); window .onDrawFrame = (){ window .render(scene); }; window .scheduleFrame(); }
参考 在 main.dart 中添加 debugRepaintRainbowEnabled = true;
,或者通过 devtools 可以观察到App的layer树
Flutter Framework 源码解析( 2 )—— 图层详解
从源码看flutter(四):Layer篇
Flutter画面渲染全面解析