欧 美 做 爱 视频网站,开发平台 华为,自学网站编程,游戏制作软件免费下载一、 Widget - Element - RenderObject关系 二、 Widget 、Element 、RenderObject 分别表示什么
2.1 Widget Widget描述和配置子树的样子 Widget就是一个个描述文件#xff0c;这些描述文件在我们进行状态改变时会不断的build。但是对于渲染对象来说#xff0c;只会使用最…一、 Widget - Element - RenderObject关系 二、 Widget 、Element 、RenderObject 分别表示什么
2.1 Widget Widget描述和配置子树的样子 Widget就是一个个描述文件这些描述文件在我们进行状态改变时会不断的build。但是对于渲染对象来说只会使用最小的开销来更新渲染界面 2.2、Element Element是一个Widget的实例在树中详细的位置。Widget描述和配置子树的样子而Element实际去配置在Element树中特定的位置 2.3、RenderObject 渲染树上的一个对象。RenderObject层是渲染库的核心 三、js生成的HTML代码和Element的理解
Element其实就相当于React中的虚拟DOM我们先来理解一下前端里面的虚拟DOM。
当我们书写js生成的HTML代码这时候会直接操作真实的DOM操作真实DOM是非常消耗性能的所以React和Vue都有虚拟DOM的概念什么意思呢就是当我们通过js操作HTML我们会先去操作虚拟DOM虚拟DOM中通过diff算法判断哪些DOM需要修改甚至不需要修改最后把虚拟DOM打个补丁到真实DOM上这样做的好处就是我们可以以最小的开销来更新真实的DOM。 我们再看一下上面的三棵树Widget就相当于HTML代码Element就相当于虚拟DOMRender就相当于真实DOM
当我们创建一个Widget的时候我们也许就不需要创建一个新的Render对象我们先去看看保存的Element的类型和key是否一致如果一致就直接修改属性即可这样我们就没必要创建新的Render Object也许只是修改其中某个属性就行这样就做到了以最小的开销来更新Render Object。
四 、Flutter 的渲染流程大致如下
4.1 构建widget树首先你的应用程序会构建一个由widget组成的树。这些widget描述了应用程序的用户界面。
我们先给widget做个分类 这些是组件Widget不会生成RenderObject Container() Text() HYHomeContent() 这些是渲染Widget会生成RenderObject Padding() Row() 我们这里以Padding为例Padding是用来设置内边距我们看看这个Widget最后怎么生成RenderObject的。
2.1. Widget
Padding是一个Widget并且继承自SingleChildRenderObjectWidget
继承关系如下
Padding - SingleChildRenderObjectWidget - RenderObjectWidget - WidgetContainer继承关系如下
Container - StatelessWidget - Widget我们之前在创建Widget时经常使用StatelessWidget和StatefulWidget这种Widget只是将其他的Widget在build方法中组装起来并不是一个真正可以渲染的Widget在之前的课程中其实有提到。
在Padding的类中我们找不到任何和渲染相关的代码这是因为Padding仅仅作为一个配置信息这个配置信息会随着我们设置的属性不同频繁的销毁和创建。 问题频繁的销毁和创建会不会影响Flutter的性能呢
并不会答案在我的另一篇文章中https://mp.weixin.qq.com/s/J4XoXJHJSmn8VaMoz3BZJQ
那么真正的渲染相关的代码在哪里执行呢
RenderObjectWidget
2.2. RenderObjectWidget
我们来看Padding里面的代码有一个非常重要的方法
这个方法其实是来自RenderObjectWidget的类在这个类中它是一个抽象方法抽象方法是必须被子类实现的但是它的子类SingleChildRenderObjectWidget也是一个抽象类所以可以不实现父类的抽象方法但是Padding不是一个抽象类必须在这里实现对应的抽象方法而它的实现就是下面的实现
override
RenderPadding createRenderObject(BuildContext context) {return RenderPadding(padding: padding,textDirection: Directionality.of(context),);
}
上面的代码创建了什么呢RenderPadding
RenderPadding的继承关系是什么呢
RenderPadding - RenderShiftedBox - RenderBox - RenderObject
我们来具体查看一下RenderPadding的源代码
如果传入的_padding和原来保存的value一样那么直接return如果不一致调用_markNeedResolution而_markNeedResolution内部调用了markNeedsLayout而markNeedsLayout的目的就是标记在下一帧绘制时需要重新布局performLayout如果我们找的是Opacity那么RenderOpacity是调用markNeedsPaintRenderOpacity中是有一个paint方法的 set padding(EdgeInsetsGeometry value) {assert(value ! null);assert(value.isNonNegative);if (_padding value)return;_padding value;_markNeedResolution();}
总结 Widget只是描述了配置信息
其中包含createElement方法用于创建Element也包含createRenderObject但是不是自己在调用
4.2 构建element树然后每个widget会被转换成一个element。这些element也构成了一棵树。
我们来思考一个问题
之前我们写的大量的Widget在树结构中存在引用关系但是Widget会被不断的销毁和重建那么意味着这棵树非常不稳定那么由谁来维系整个Flutter应用程序的树形结构的稳定呢答案就是Element。官方的描述Element是一个Widget的实例在树中详细的位置。
我们再研究Padding是怎么创建Element的我们进入Widget类里面发现有个createElement()方法
protected
factory
Element createElement();
因为Widget是个抽象类所以createElement方法必须被它的子类实现。我们也可以得出一个结论只要你是一个widget无论是不是渲染的widget都要实现createElement方法只不过每个类实现的不一样。
我们发现对于Padding是父类SingleChildRenderObjectWidget实现了这个方法最后返回的是SingleChildRenderObjectElement。 override SingleChildRenderObjectElement createElement() SingleChildRenderObjectElement(this);
对于Container也是它的父类StatelessWidget实现了createElement方法 overrideStatelessElement createElement() StatelessElement(this);
同理StatefulWidget也实现了createElement方法
overrideStatefulElement createElement() StatefulElement(this);
它们返回的对象不同一个是StatelessElement一个是StatefulElement只不过都继承于ComponentElement。它们的区别就是StatefulElement会多一个state属性。小总结
我们写一个widget对于渲染widget会创建RenderObject每一个widget都会创建一个Element对象在创建完一个Element之后Flutter引擎会调用mount方法来将Element插入到树中具体的位置
Element什么时候创建
在每一次创建Widget的时候会创建一个对应的Element然后将该元素插入树中。
在SingleChildRenderObjectWidget中我们可以找到如下代码
在Widget中Element被创建并且在创建时将thisWidget传入了Element就保存了对Widget的应用
override
SingleChildRenderObjectElement createElement() SingleChildRenderObjectElement(this);
在创建完一个Element之后Flutter引擎会调用mount方法来将Element插入到树中具体的位置再Element类中我们会找到如下代码 进入ComponentElement源码查看ComponentElement的mount的执行过程代码比较繁琐可以直接看下面总结。
abstract class ComponentElement extends Element {/// Creates an element that uses the given widget as its configuration.ComponentElement(super.widget);Element? _child;bool _debugDoingBuild false;overridebool get debugDoingBuild _debugDoingBuild;override// 1. 调用mount方法void mount(Element? parent, Object? newSlot) {super.mount(parent, newSlot);assert(_child null);assert(_lifecycleState _ElementLifecycle.active);// 2. 调用_firstBuild_firstBuild();assert(_child ! null);}void _firstBuild() {// StatefulElement overrides this to also call state.didChangeDependencies.// 3. 调用rebuildrebuild(); // This eventually calls performRebuild.}/// Calls the [StatelessWidget.build] method of the [StatelessWidget] object/// (for stateless widgets) or the [State.build] method of the [State] object/// (for stateful widgets) and then updates the widget tree.////// Called automatically during [mount] to generate the first build, and by/// [rebuild] when the element needs updating.overridepragma(vm:notify-debugger-on-exception)// 6. 这是performRebuildvoid performRebuild() {assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));// 8. 就是这个WidgetWidget? built;try {assert(() {_debugDoingBuild true;return true;}());// 7. 调用build方法生成一个Widgetbuilt build();assert(() {_debugDoingBuild false;return true;}());debugWidgetBuilderValue(widget, built);} catch (e, stack) {_debugDoingBuild false;built ErrorWidget.builder(_debugReportException(ErrorDescription(building $this),e,stack,informationCollector: () DiagnosticsNode[if (kDebugMode)DiagnosticsDebugCreator(DebugCreator(this)),],),);} finally {// We delay marking the element as clean until after calling build() so// that attempts to markNeedsBuild() during build() will be ignored._dirty false;assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));}try {_child updateChild(_child, built, slot);assert(_child ! null);} catch (e, stack) {built ErrorWidget.builder(_debugReportException(ErrorDescription(building $this),e,stack,informationCollector: () DiagnosticsNode[if (kDebugMode)DiagnosticsDebugCreator(DebugCreator(this)),],),);_child updateChild(null, built, slot);}}/// Subclasses should override this function to actually call the appropriate/// build function (e.g., [StatelessWidget.build] or [State.build]) for/// their widget.protectedWidget build();overridevoid visitChildren(ElementVisitor visitor) {if (_child ! null) {visitor(_child!);}}overridevoid forgetChild(Element child) {assert(child _child);_child null;super.forgetChild(child);}
}// 4. 这是rebuild
void rebuild() {assert(_lifecycleState ! _ElementLifecycle.initial);if (_lifecycleState ! _ElementLifecycle.active || !_dirty) {return;}Element? debugPreviousBuildTarget;performRebuild();
}/// Cause the widget to update itself.
///
/// Called by [rebuild] after the appropriate checks have been made.
protected
// 5. 调用performRebuild
void performRebuild();
}class StatelessElement extends ComponentElement {/// Creates an element that uses the given widget as its configuration.StatelessElement(StatelessWidget super.widget);override// 9. 拿到widget,调用widget的build方法// 这个widget就是创建element的时候传进来的widgetWidget build() (widget as StatelessWidget).build(this);overridevoid update(StatelessWidget newWidget) {super.update(newWidget);assert(widget newWidget);_dirty true;rebuild();}
}
上面1-9步看起来比较复杂其实就是
mount方法 - firstBuild - rebuild - performBuild - build - _widget的build这里的_widget就是创建element的时候传进来的widget。
我们都知道build方法有个参数build(Build Context context)所以这个context其实就是element这个context最主要的作用就是告诉我们构建的element在树里面的哪个位置之后可以沿着树去查找一些信息。
如果是statefulWidget它里面的build方法如下
override
Widget build() state.build(this);
我们发现它之后调用了state.build(this)而不是 (widget as StatelessWidget).build(this);
下面我们看看SingleChildRenderObjectElement的mount方法的调用过程。
在调用mount方法时会同时使用Widget来创建RenderObject并且保持对RenderObject的引用创建完RenderObject之后再把RenderObject挂载到RenderObjectTree树的某个位置 overridevoid mount(Element parent, dynamic newSlot) {super.mount(parent, newSlot);// 就是这行代码创建RenderObject_renderObject widget.createRenderObject(this);assert(() {_debugUpdateRenderObjectOwner();return true;}());assert(_slot newSlot);attachRenderObject(newSlot);_dirty false;}
下面说一下StatefulElement它是继承于ComponentElement的所以ComponentElement有的方法它都有 StatefulElement(StatefulWidget widget)// 1. 就是这里调用了createState: _state widget.createState(),super(widget) {assert(() {if (!state._debugTypesAreRight(widget)) {throw FlutterError.fromParts(DiagnosticsNode[ErrorSummary(StatefulWidget.createState must return a subtype of State${widget.runtimeType}),ErrorDescription(The createState function for ${widget.runtimeType} returned a state of type ${state.runtimeType}, which is not a subtype of State${widget.runtimeType}, violating the contract for createState.,),]);}return true;}());assert(state._element null);state._element this;assert(state._widget null,The createState function for $widget returned an old or invalid state instance: ${state._widget}, which is not null, violating the contract for createState.,);// 2. 然后将widget赋值给state里面的_widgetstate._widget widget;assert(state._debugLifecycleState _StateLifecycle.created);}
上面主要做了两件事
StatefulElement的构造器中调用了widget.createState()方法将widget赋值给state里面的_widget正是因为这样我们在state里面才可以通过this.widget拿到对应的widget
总结
widget创建完之后Flutter框架一定会根据widget创建一个element创建完之后会调用element的mount方法最后根据一系列的调用会调用widget的build(Build Context context)方法。如果是renderElement那么它的mount主要做的就是创建一个_renderObject如果是StatefulElement那么会调用调用了createState然后将widget赋值给state里面的_widget
2.4. build的context是什么
在StatelessElement中我们发现是将this传入所以本质上BuildContext就是当前的Element。
Widget build() widget.build(this);我们来看一下继承关系图
Element是实现了BuildContext类隐式接口 abstract class Element extends DiagnosticableTree implements BuildContext
在StatefulElement中build方法也是类似调用state的build方式时传入的是this。
Widget build() state.build(this); 小结Element是真正保存树结构的对象
创建出来后会由framework调用mount方法在mount方法中会调用widget的createRenderObject对象并且Element对widget和RenderObject都有引用
4.1 构建widget树首先你的应用程序会构建一个由widget组成的树。这些widget描述了应用程序的用户界面。
4.2 构建element树然后每个widget会被转换成一个element。这些element也构成了一棵树。
4.3 调用renderObjectFlutter会为每个需要显示的element创建一个对应的RenderObject。这些RenderObject会构成一个树它用于实际进行渲染。
RenderObject是真正渲染的对象
其中有markNeedsLayout performLayout markNeedsPaint paint等方法
4.4 布局layout然后Flutter会遍历RenderObject树计算每个节点的位置和大小。
4.5 绘制paint接下来Flutter会再次遍历RenderObject树调用每个节点的paint方法进行绘制。
4.6 GPU加速渲染最后将绘制指令发送给GPU最终显示在屏幕上。