参考
http://gityuan.com/flutter/
Flutter完整开发实战详解(二十一、 Flutter 画面渲染的全面解析)
layout
请求layout
首次
RendererBinding
@override void initInstances() { 。。。 initRenderView(); 。。。 } void initRenderView() { renderView = RenderView(configuration: createViewConfiguration(), window: window); renderView.prepareInitialFrame(); }
RenderView
void prepareInitialFrame() { scheduleInitialLayout(); scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer()); } void scheduleInitialLayout() { _relayoutBoundary = this; owner!._nodesNeedingLayout.add(this); }
owner是PipelineOwner。
上边流程可知,初始化时会把root加入到需要布局list中,那么在第一帧到来时就会调用其layout进行布局。
markNeedsLayout
上边是初始时的必须的layout,如果在ui运行起来后布局参数发生了变化需要重新布局就需要调用此方法:
void markNeedsLayout() { if (_needsLayout) { return; } if (_relayoutBoundary != this) { markParentNeedsLayout(); } else { _needsLayout = true; if (owner != null) { owner!._nodesNeedingLayout.add(this); owner!.requestVisualUpdate(); } } }
这里涉及到了relayoutBoundary的概念,flutter会把从上到下相邻的几个renderObject按照规则当成一组来处理layout,规则是如果子renderObject的layout会影响父renderObject的大小、位置,那么就会把这些相互影响的renderObject当成一组。这样做的目的很明显,就是为了减少不必要的layout。
markNeedsLayout()中如果relayoutBoundary和当前的renderObject不相等,那说明它和它的parent是一组,那么就会去请求parent layout:
void markParentNeedsLayout() { _needsLayout = true; finaRenderObject parent = this.parent! as RenderObject; // 如果当前没有处于layout阶段,那么就 请求layout if (!_doingThisLayoutWithCallback) { parent.markNeedsLayout(); } }
如果relayoutBoundary和当前的renderObject相等,就会把当前的renderObject加入到一个需要layout的list中,并请求一下PipelineOwner.requestVisualUpdate:
void requestVisualUpdate() { if (onNeedVisualUpdate != null) onNeedVisualUpdate!(); }
onNeedVisualUpdate是一个VoidCallback,它是在RendererBinding.initInstances中加入的
void initInstances() { super.initInstances(); _instance = this; _pipelineOwner = PipelineOwner( onNeedVisualUpdate: ensureVisualUpdate, onSemanticsOwnerCreated: _handleSemanticsOwnerCreated, onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed, ); } void ensureVisualUpdate() { switch (schedulerPhase) { case SchedulerPhase.idle: case SchedulerPhase.postFrameCallbacks: scheduleFrame(); return; case SchedulerPhase.transientCallbacks: case SchedulerPhase.midFrameMicrotasks: case SchedulerPhase.persistentCallbacks: return; } } void scheduleFrame() { if (_hasScheduledFrame || !framesEnabled) return; ensureFrameCallbacksRegistered(); window.scheduleFrame(); _hasScheduledFrame = true; }
最后会去调用底层window.scheduleFrame()来注册一个下一帧时回调,就类似于Android中的ViewRootImpl.scheduleTraversals()。
触发layout
下一帧来到后,会调用到的方法是在上边的ensureFrameCallbacksRegistered()中注册的回调,
SchedulerBinding.ensureFrameCallbacksRegistered
void ensureFrameCallbacksRegistered() { window.onBeginFrame ??= _handleBeginFrame; window.onDrawFrame ??= _handleDrawFrame; }
onBeginFrame :主要是用来执行动画,
onDrawFrame :这个主要处理上边说的persistentCallbacks。
void handleDrawFrame() { try { // PERSISTENT FRAME CALLBACKS _schedulerPhase = SchedulerPhase.persistentCallbacks; for (finaFrameCallback callback in _persistentCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp!); // POST-FRAME CALLBACKS _schedulerPhase = SchedulerPhase.postFrameCallbacks; finaList<FrameCallback> localPostFrameCallbacks = List<FrameCallback>.from(_postFrameCallbacks); _postFrameCallbacks.clear(); for (finaFrameCallback callback in localPostFrameCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp!); } finally { } }
看下persistentCallbacks列表在哪添加的callback,最终找到是在RendererBinding.initInstances中添加的callback,
void initInstances() { addPersistentFrameCallback(_handlePersistentFrameCallback); }
接着上边handleDrawFrame的流程,RendererBinding中:
void _handlePersistentFrameCallback(Duration timeStamp) { drawFrame(); } 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; } }
pipelineOwner.flushLayout()
void flushLayout() { try { while (_nodesNeedingLayout.isNotEmpty) { finaList<RenderObject> dirtyNodes = _nodesNeedingLayout; _nodesNeedingLayout = <RenderObject>[]; for (finaRenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) { if (node._needsLayout && node.owner == this) node._layoutWithoutResize(); } } } finally { } }
会按照从上到下的顺序进行layout。
RenderObject._layoutWithoutResize()
void _layoutWithoutResize() { try { performLayout(); } catch (e, stack) { } _needsLayout = false; markNeedsPaint(); }
可以看到会先执行performLayout,然后又调用了markNeedsPaint(),因为有可能改变了大小位置会影响绘制的内容。
layout流程
一般父renderObject在performLayout时是会调用child的layout的
void layout(Constraints constraints, { booparentUsesSize = false }) { if (!kReleaseMode && debugProfileLayoutsEnabled) Timeline.startSync('$runtimeType', arguments: timelineArgumentsIndicatingLandmarkEvent); RenderObject? relayoutBoundary; if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) { relayoutBoundary = this; } else { relayoutBoundary = (parent as RenderObject)._relayoutBoundary; } // 如果当前就是relayoutBoundary,并且没被请求layout,constraints 也和之前一样,那么直接return。 if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) { if (!kReleaseMode && debugProfileLayoutsEnabled) Timeline.finishSync(); return; } _constraints = constraints; if (_relayoutBoundary != nul&& relayoutBoundary != _relayoutBoundary) { // The locarelayout boundary has changed, must notify children in case // they also need updating. Otherwise, they wilbe confused about what // their actuarelayout boundary is later. visitChildren(_cleanChildRelayoutBoundary); } _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(); if (!kReleaseMode && debugProfileLayoutsEnabled) Timeline.finishSync(); }
1. 确定当前RenderObject对应的relayoutBoundary,就是需要重新布局的边界或范围。
2. 调用performResize或performLayout去确定自己的size
- 参数constraints代表了parent传入的约束,最后计算得到的RenderObject的size必须符合这个约束。
- 参数parentUsesSize代表parent是否会使用child的size,它参与计算repaintBoundary,可以对Layout过程起到优化作用。
- sizedByParent是RenderObject的一个属性,默认为false,子类可以去重写这个属性。顾名思义,sizedByParent表示RenderObject的size的计算完全由其parent决定。换句话说,也就是RenderObject的size只和parent给的constraints有关,与自己children的sizes无关。同时,sizedByParent也决定了RenderObject的size需要在哪个方法中确定,若sizedByParent为true,那么size必须得在performResize方法中确定,否则size需要在performLayout中确定。
- performResize()方法的作用是确定size,实现该方法时需要根据parent传入的constraints确定RenderObject的size。
- performLayout()则除了用于确定size以外,还需要负责遍历调用child.layout方法对计算children的sizes和offsets。
和Android的layout的不同
- Android上请求layout时,会把请求layout的view 及其 祖先view都标记一下表示需要layout,那么下一帧时就会从顶层view去遍历所有的view,如果遇到标记需要layout的view就继续layout,如果遇到没有标记layout的view那么就会跳过这个view及其子树。
- 而flutter上请求layout时,只会把请求layout的那一组renderObject标记上需要layout,并把relayoutBoundary的renderObject加入到需要layout的list中,那么在下一帧时就会从list中取出需要layout的relayoutBoundary的renderObject来layout。
总的来看,好像flutter的layout会更有效率一些,因为加入了一个分层的概念。
paint
只有在layout之后才能进行绘制。如果发现要绘制的renderObject的needLayout为true,那么就直接返回不绘制了,因为此时绘制无意义,重新布局后也会进行重绘。
layer
绘制涉及到一个Layer和RepaintBoundary的概念,其实和Android硬件加速的DisplayList是类似的东西。
一个layer中的所有renderObject都会把其绘制命令保存在一个layer上,一个layer中只要有一个renderObject调用markNeedPaint(不管其在哪层),那么同一个layer的所有的renderObject都会被重绘,如果其中有一个renderObject的一个子renderObject是RepaintBoundary(也就是一个新的layer),那么可以直接复用其缓存。
其实就是减少不必要的重绘。
flutter就提供了一个现成的widget——RepaintBoundary,来让我们方便的把一个组件变为一个新的layer。
Layer的类型
flutter中有很多种Layer,主要分为ContainerLayer和 叶子Layer。
ContainerLayer 是可以具备子节点,也就是带有 append 方法,大致可以分为:
- 位移类(OffsetLayer/TransformLayer);
- 透明类(OpacityLayer)
- 裁剪类(ClipRectLayer/ClipRRectLayer/ClipPathLayer);
- 阴影类 (PhysicalModelLayer)
- ShaderMaskLayer:着色层,可以指定着色器矩阵和混合模式参数。
- ColorFilterLayer:颜色过滤层,可以指定颜色和混合模式参数。
- BackdropFilterLayer:背景过滤层,可以指定背景图参数。
为什么这些 Layer 需要是 ContainerLayer ?因为这些 Layer 都是一些像素合成的操作,其本身是不具备“描绘”控件的能力,就如前面的蓝色小方块例子一样,如果要呈现画面一般需要和 PictureLayer 结合。
比如 RenderClipRRect内部,在 pushClipRRect 时可以会创建 ClipRRectLayer ,而新创建的 ClipRRectLayer 会通过 appendLayer 方法触发 append 操作添加为父 Layer 的子节点。
叶子Layer一般不具备子节点,比如:
- PictureLayer 是用于绘制画面,Flutter 上的控件基本是绘制在这上面;
- TextureLayer 是用于外界纹理,比如视频播放或者摄像头数据;
- PlatformViewLayer 是用于 iOS 上 PlatformView 相关嵌入纹理的使用;
请求paint
首次
RendererBinding
@override void initInstances() { 。。。 initRenderView(); 。。。 } void initRenderView() { renderView = RenderView(configuration: createViewConfiguration(), window: window); renderView.prepareInitialFrame(); }
RenderView
void prepareInitialFrame() { scheduleInitialLayout(); scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer()); } void scheduleInitialPaint(ContainerLayer rootLayer) { _layer = rootLayer; owner!._nodesNeedingPaint.add(this); }
上边流程可知,初始化时会把root加入到需要重绘list中,那么在第一帧到来时就会重绘。
markNeedsPaint
对于非第一次,需要调用RenderObject.markNeedsPaint。
void markNeedsPaint() { if (_needsPaint) return; _needsPaint = true; if (isRepaintBoundary) { // If we always have our own layer, then we can just repaint // ourselves without involving any other nodes. if (owner != null) { owner!._nodesNeedingPaint.add(this); owner!.requestVisualUpdate(); } } else if (parent is RenderObject) { final RenderObject parent = this.parent as RenderObject; parent.markNeedsPaint(); } else { // 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) owner!.requestVisualUpdate(); } }
此方法分为三个逻辑:
1. 如果是RepaintBoundary,那么就把它加入到_nodesNeedingPaint,并请求下一帧回调。
2. 如果不是RepaintBoundary,那么就往上找,
3. 始终想不通为什么会调用这里,因为RenderView.isRepaintBoundary为true。
PipelineOwner.requestVisualUpdate在上边的layout也有,所以直接看下一帧来时的重绘流程。
触发paint
在上边的触发layout中的drawFrame中有一步就是进行重绘的:
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; } }
pipelineOwner.flushPaint()
void flushPaint() { try { final List<RenderObject> dirtyNodes = _nodesNeedingPaint; _nodesNeedingPaint = <RenderObject>[]; // Sort the dirty nodes in reverse order (deepest first). 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 { } }
PaintingContext.repaintCompositedChild
static void repaintCompositedChild(RenderObject child, { bool debugAlsoPaintedParent = false }) { assert(child._needsPaint); _repaintCompositedChild( child, debugAlsoPaintedParent: debugAlsoPaintedParent, ); } static void _repaintCompositedChild( RenderObject child, { bool debugAlsoPaintedParent = false, PaintingContext? childContext, }) { OffsetLayer? childLayer = child._layer as OffsetLayer?; if (childLayer == null) { // Not using the `layer` setter because the setter asserts that we not // replace the layer for repaint boundaries. That assertion does not // apply here because this is exactly the place designed to create a // layer for repaint boundaries. child._layer = childLayer = OffsetLayer(); } else { childLayer.removeAllChildren(); } childContext ??= PaintingContext(child._layer!, child.paintBounds); child._paintWithContext(childContext, Offset.zero); childContext.stopRecordingIfNeeded(); }
由于加入到_nodesNeedingPaint中的RenderObject都是RepaintBoundary,也就是一个layer所有者,所以就先看是否需要创建一个OffsetLayer,
如果不需要就把之前的绘制子layer都给清空了,便于之后重绘。
接着就是重建一个PaintingContext对象,并调用renderObject的_paintWithContext来进行往下重绘。
RenderObject._paintWithContext
void _paintWithContext(PaintingContext context, Offset offset) { if (_needsLayout) return; _needsPaint = false; try { paint(context, offset); } catch (e, stack) { } }
你可能会有疑问,就是如果RepaintBoundary的RenderObject也需要绘制自身,而它的layer是一个ContainerLayer并不能绘制,怎么办呢?
其实PaintingContext中首次访问canvas时会进行一些初始化:
Canvas get canvas { if (_canvas == null) _startRecording(); return _canvas!; } void _startRecording() { assert(!_isRecording); _currentLayer = PictureLayer(estimatedBounds); _recorder = ui.PictureRecorder(); _canvas = Canvas(_recorder!); _containerLayer.append(_currentLayer!); }
可以看到这里边创建了一个子layer——PictureLayer用于该layer中需要绘制内容的一个存放点。
paint
一般父renderObject会调用PaintingContext.paintChild来绘制child。
void paintChild(RenderObject child, Offset offset) { if (child.isRepaintBoundary) { stopRecordingIfNeeded(); _compositeChild(child, offset); } else { child._paintWithContext(this, offset); } }
这个方法分两个逻辑:
1. 如果child是RepaintBoundary,也就是另一个layer,那么就需要停止当前layer的绘制,进行子layer的绘制
2. 如果不是RepaintBoundary,那么就继续调用child.paint,把child绘制在当前layer上。
停止当前layer的绘制,PaintingContext
void stopRecordingIfNeeded() { if (!_isRecording) return; _currentLayer!.picture = _recorder!.endRecording(); _currentLayer = null; _recorder = null; _canvas = null; }
进行子layer的绘制,PaintingContext
void _compositeChild(RenderObject child, Offset offset) { // Create a layer for our child, and paint the child into it. if (child._needsPaint) { repaintCompositedChild(child, debugAlsoPaintedParent: true); } else { } assert(child._layer is OffsetLayer); final OffsetLayer childOffsetLayer = child._layer as OffsetLayer; childOffsetLayer.offset = offset; appendLayer(child._layer!); } void appendLayer(Layer layer) { layer.remove(); _containerLayer.append(layer); }
这里边也分两步,
1. 首先是判读是否child需要重绘,如果需要才进行repaintCompositedChild(在触发paint介绍过了),也就是绘制子layer,
2. 然后就是把子layer添加到父layer中,如果没有重绘,子layer就是之前绘制的一个缓存,从而构成了一个layer tree。