什么是RenderObject
官方定义:An object in the render tree. 渲染树中的一个对象。
正如官方解释,RenderObject就是渲染树中的一个对象,负责布局及绘制。它有一个父级,并有一个名为parentData的插槽,其中父级RenderObject可以存储特定于子级的数据,例如子级位置。RenderObject类也实现了基本的布局和绘制协议。但是,RenderObject类没有定义子模型(例如,节点是否有零个,一个或多个子节点)。它也没有定义坐标系(例如,子级是否位于笛卡尔坐标系,极坐标系等)或特定的布局协议(例如布局是宽度高度还是尺寸约束或者父级在子级布置之前还是之后设置子级的大小和位置等;或者确实是否允许子级读取他们父级的parentData插槽)。
对于parentData解释:
在 Flutter 的布局系统中,该类负责存储父节点所需要的子节点的布局信息,当然该信息偶尔也会用于子节点的布局。 每个 RenderObject 类中都有 parentData 这样一个成员,该成员只能通过 setupParentData 方法赋值,RenderObject 的子类可以通过重写该方法将 ParentData 的子类赋值给 parentdata,以扩展 ParentData 的功能。
RenderObejct继承关系
通过 Android Studio 的 Hierarchy 功能可以直观地对类继承关系进行查看:
- RenderBoxA render object in a 2D Cartesian coordinate system.
采用2D笛卡尔坐标系中的渲染对象。它实现了一个内在的尺寸调整协议,它允许您在没有完全铺设的情况下测量一个子级,以这样的方式,如果该子级改变了尺寸,父级将再次布置(考虑到子级的新尺寸)。若对坐标系统没有限制,可直接继承它来实现自定义RenderObject。 - RenderView
The root of the render tree.渲染对象树的根。它有单独的子级,它必须是一个RenderBox。因此,如果你想在渲染树中有一个自定义的RenderObject子类,你有两种选择:你可能需要替换RenderView本身,或者你需要一个RenderBox作为它的子类。 - RenderAbstractViewport
An interface for render objects that are bigger on the inside.内部较大的渲染对象的界面。某些渲染对象(如RenderViewport)显示其内容的一部分,可以通过ViewportOffset进行控制。这个接口允许框架识别这些呈现对象并与它们交互,而不需要了解所有不同类型的视图。主要处理滑动相关控件的展示。 - RenderSliver
Base class for the render objects that implement scroll effects in viewports.在视图中实现滚动效果的渲染对象的基类。Sliver有细片、薄片之意,在Flutter中,Sliver通常指可滚动组件子元素(就像一个个薄片一样)。只有当Sliver出现在视口中时才会去构建它,这种模型也称为“基于Sliver的延迟构建模型”。
RenderViewport有一组子Sliver。每个Sliver(字面意思是视图内容的一部分)依次排列,覆盖过程中的视图(每个Sliver每次都被放置,包括那些由于“滚动”或超出了视图端口的末端而没有区段的Sliver。)。而RenderSliver则控制着Sliver的绘制渲染。
此外RenderObjec还有两个常用的mixin:
RenderObjectWithChildMixin 用于为只有 1 个 child 的 RenderObject 提供 child 管理模型。
ContainerRenderObjectMixin 用于为有多个 child 的 RenderObject 提供 child 管理模型。
基本上每个 RenderBox 都混入了他们,省去了自己管理 child 的代码。
Widget、Element及RenderObject关系
- Widget实际上就是Element的配置数据,Widget树实际上是一个配置树,而真正的UI渲染树是由Element构成。Widget只是描述显示元素的一个配置数据,真正代表屏幕上显示元素的类是Element。
- 一个Widget对象可以对应多个Element对象。(相同的widget可以同时存在)
- UI树由一个个独立的Element节点构成。组件最终的Layout、渲染都是通过RenderObejct来完成的,从创建到渲染的大体流程是:根据Widget生成Element,然后创建相应的RenderObejct并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。
- 我们可以认为Flutter的UI系统包含三棵树:Widget树、Element树、渲染树。他们的依赖关系是:Element树根据Widget树生成,而渲染树又依赖于Element树。
工作流程
以我们的程序入口runApp来说,我们来分析一下究竟是如何将界面渲染绘制出来的。
void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() ..attachRootWidget(app) ..scheduleWarmUpFrame(); }
- runApp 接受一个weight,即我们要显示的界面
- WidgetsFlutterBinding 将Widget架构和Flutter底层Engine连接的桥梁
- ensureInitialized 初始化操作
- attachRootWidget 完成了Widget到Element到RenderObject的整个关联过程核心流程:1.将传入的widget包装进RenderObjectToWidgetAdapter(继承自RenderObjectWidget),RenderObjectToWidgetAdapter负责将Widget、Element、RenderObject三者关联
2.创建Element,与widget绑定,即根据WidgetTree创建出对应的ElementTree
3.创建对应的RenderObject,并attach到对应的slot位置
- scheduleWarmUpFrame 经过attachRootWidget,Widget、Element及RenderObject三者已经完成了关联,并构建成树。接下来通过scheduleWarmUpFrame绘制,具体实现在SchedulerBinding。
我们详细分析一下绘制流程:
void scheduleWarmUpFrame() { if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle) return; _warmUpFrame = true; Timeline.startSync('Warm-up frame'); final bool hadScheduledFrame = _hasScheduledFrame; // We use timers here to ensure that microtasks flush in between. Timer.run(() { assert(_warmUpFrame); handleBeginFrame(null); }); Timer.run(() { assert(_warmUpFrame); handleDrawFrame(); resetEpoch(); _warmUpFrame = false; if (hadScheduledFrame) scheduleFrame(); }); lockEvents(() async { await endOfFrame; Timeline.finishSync(); }); }
可以看到,绘制的核心方法分为两个:handleBeginFrame() 和 handleDrawFrame();
handleBeginFrame()
Called by the engine to prepare the framework to produce a new frame.
从官方解释看,该方式是framework生成一个新的frame的时候,引擎调用的。何为frame?
Frame即每一帧的绘制过程,engine通过VSync信号不断地触发Frame的绘制,实际上就是调用SchedulerBinding类中的_handleBeginFrame()和_handleDrawFrame()这两个方法,这个过程中会完成动画、布局、绘制等工作。
void handleBeginFrame(Duration rawTimeStamp) { Timeline.startSync('Frame', arguments: timelineWhitelistArguments); _firstRawTimeStampInEpoch ??= rawTimeStamp; _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp); if (rawTimeStamp != null) _lastRawTimeStamp = rawTimeStamp; if (!kReleaseMode) { _profileFrameNumber += 1; _profileFrameStopwatch.reset(); _profileFrameStopwatch.start(); } _hasScheduledFrame = false; try { // TRANSIENT FRAME CALLBACKS Timeline.startSync('Animate', arguments: timelineWhitelistArguments); _schedulerPhase = SchedulerPhase.transientCallbacks; final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks; _transientCallbacks = <int, _FrameCallbackEntry>{}; callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) { if (!_removedIds.contains(id)) _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack); }); _removedIds.clear(); } finally { _schedulerPhase = SchedulerPhase.midFrameMicrotasks; } }
可以看到该方法主要遍历transientCallbacks列表,然后回调
handleDrawFrame()
void handleDrawFrame() { assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks); Timeline.finishSync(); // end the "Animate" phase try { // PERSISTENT FRAME CALLBACKS _schedulerPhase = SchedulerPhase.persistentCallbacks; for (FrameCallback callback in _persistentCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp); // POST-FRAME CALLBACKS _schedulerPhase = SchedulerPhase.postFrameCallbacks; final List<FrameCallback> localPostFrameCallbacks = List<FrameCallback>.from(_postFrameCallbacks); _postFrameCallbacks.clear(); for (FrameCallback callback in localPostFrameCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp); } finally { _schedulerPhase = SchedulerPhase.idle; Timeline.finishSync(); // end the Frame if (!kReleaseMode) { _profileFrameStopwatch.stop(); _profileFramePostEvent(); } _currentFrameTimeStamp = null; } }
与handleBeginFrame()类似,遍历persistentCallbacks及postFrameCallbacks列表,然后回调
transientCallbacks、persistentCallbacks及postFrameCallbacks这三个回调列表的作用是什么呢?
Frame绘制期间,有三个callbacks列表会被调用,这三个列表是SchedulerBinding类中的成员,它们的调用顺序如下:
- transientCallbacks,由Ticker触发和停止,一般用于动画的回调。
- persistentCallbacks,永久callback,一经添加无法移除,由WidgetsBinding.instance.addPersitentFrameCallback()注册,这个回调处理了布局与绘制工作。
- postFrameCallbacks,只会调用一次,调用后会被系统移除,可由WidgetsBinding.instance.addPostFrameCallback()注册,该回调一般用于State的更新。
上面篇幅中我们说到WidgetsFlutterBinding,其在创建的时候绑定了WidgetsBinding(绑定组件树)、RendererBinding(绑定渲染树)、SemanticsBinding(绑定语义树)、PaintingBinding(绑定绘制操作)、
SchedulerBinding(绑定帧绘制回调函数,以及widget生命周期相关事件)、ServicesBinding(绑定平台服务消息,注册Dart层和C++层的消息传输服务)、GestureBinding(绑定手势事件,用于检测应用的各种手势相关操作)等mixin
RendererBinding在初始化的时候通过addPersistentFrameCallback(_handlePersistentFrameCallback)方法将_handlePersistentFrameCallback回调注册到了persistentCallbacks中,所以通过回调最终回调到WidgetsBinding mixin中的drawFrame(),而这里才是真正进行绘制的地方:
@override void drawFrame() { assert(!debugBuildingDirtyElements); assert(() { debugBuildingDirtyElements = true; return true; }()); try { if (renderViewElement != null) buildOwner.buildScope(renderViewElement); super.drawFrame(); buildOwner.finalizeTree(); } finally { assert(() { debugBuildingDirtyElements = false; return true; }()); } if (!kReleaseMode) { if (_needToReportFirstFrame && _reportFirstFrame) { developer.Timeline.instantSync('Widgets completed first useful frame'); developer.postEvent('Flutter.FirstFrame', <String, dynamic>{}); _needToReportFirstFrame = false; } } }
可以看到如果renderViewElement不为空的话,即非首次绘制,会先调用buildOwner.buildScope(),该方法会将被标记为“dirty” 的 Element 进行重新构建。因为它with RendererBinding mixin,所以最终实现是在RendererBinding mixin中的drawFrame()方法。
我们来看一下RendererBinding中的drawFrame:
@protected void drawFrame() { assert(renderView != null); pipelineOwner.flushLayout(); pipelineOwner.flushCompositingBits(); pipelineOwner.flushPaint(); renderView.compositeFrame(); // this sends the bits to the GPU pipelineOwner.flushSemantics(); // this also sends the semantics to the OS. }
可以看到这里是对widgets进行布局和渲染的具体实现了,其中PipelineOwner为渲染树的管理着,维护需要绘制的View。
- flushLayout()
void flushLayout() { ... try { // TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themselves while (_nodesNeedingLayout.isNotEmpty) { final List<RenderObject> dirtyNodes = _nodesNeedingLayout; _nodesNeedingLayout = <RenderObject>[]; for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) { if (node._needsLayout && node.owner == this) node._layoutWithoutResize(); } } } ...
该方法,通过遍历需要更新的列表_nodesNeedingLayout,负责对“dirty”RenderObject重新layout。node._layoutWithoutResize()方法中调用RenderObject的performLayout()方法。
- flushCompositingBits
void flushCompositingBits() { if (!kReleaseMode) { Timeline.startSync('Compositing bits'); } _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth); for (RenderObject node in _nodesNeedingCompositingBitsUpdate) { if (node._needsCompositingBitsUpdate && node.owner == this) node._updateCompositingBits(); } _nodesNeedingCompositingBitsUpdate.clear(); if (!kReleaseMode) { Timeline.finishSync(); } }
该方法的作用是更新RenderObject._needsCompositing标示,这个涉及到一个Layer概念(我们在屏幕上看到的Flutter app页面其实是由不同的图层(layers)组合(compsite)而成的。这些图层是以树的形式组织起来的,也就是我们在Flutter中见到的又一个比较重要的树:layer tree)。如果该状态变化了,就会将该RenderObject标记为需要进行重绘。
- flushPaint()
void flushPaint() { ... final List<RenderObject> dirtyNodes = _nodesNeedingPaint; _nodesNeedingPaint = <RenderObject>[]; // Sort the dirty nodes in reverse order (deepest first). for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) { assert(node._layer != null); if (node._needsPaint && node.owner == this) { if (node._layer.attached) { PaintingContext.repaintCompositedChild(node); } else { node._skippedPaintingOnLayer(); } } } ... }
可以看到该方法从列表中取出需要重绘RenderObject进行绘制。从RenderObject源码中可以得知调用markNeedsPaint()方法通过递归遍历,从当前节点一直往上找,直到isRepaintBoundary(该标识位标志当前节点是否与父节点分开来重绘。当这个标志位为true的时候,父节点重绘的时候子节点不一定也需要重绘,同样的,当自身重绘的时候父节点不一定需要重绘。此标志位为true的RenderObject有render tree的根节点RenderView,有我们熟悉的RenderRepaintBoundary,TextureBox等)为ture的RenderObject,将child的_needsPaint标志位置为true。
- compositeFrame()
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(); assert(() { if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled) debugCurrentRepaintColor = debugCurrentRepaintColor.withHue((debugCurrentRepaintColor.hue + 2.0) % 360.0); return true; }()); } finally { Timeline.finishSync(); } }
/// Uploads the composited layer tree to the engine.
从方法解释可以看出该方法中layer通过buildScene()转换成底层scene类型,交给engine处理,最终完成视图的渲染。