Flutter 怎么构建画面

本贴最后更新于 1987 天前,其中的信息可能已经时过境迁

Widget 是什么?

这里有一个 “总所周知” 的答就是:Widget 并不真正的渲染对象 。是的,事实上在 Flutter 中渲染是经历了从 WidgetElement 再到 RenderObject 的过程。

Widget 是不可变的,那么 Widget 是如何在不可变中去构建画面的?

上面我们知道,Widget 是需要转化为 Element 去渲染的,而从下图注释可以看到,事实上 Widget 只是 Element 的一个配置描述 ,告诉 Element 这个实例如何去渲染。

/// Describes the configuration for an [Element]. ///描述[元素]的配置。 /// Widgets are the central class hierarchy in the Flutter framework. A widget /// is an immutable description of part of a user interface. Widgets can be /// inflated into elements, which manage the underlying render tree. ///widgets是flutr框架中的中心类层次结构。小部件 ///是用户界面的一部分的不可变描述。小部件可以是 ///已膨胀为元素,用于管理底层呈现树。 /// Widgets themselves have no mutable state (all their fields must be final). /// If you wish to associate mutable state with a widget, consider using a /// [StatefulWidget], which creates a [State] object (via /// [StatefulWidget.createState]) whenever it is inflated into an element and /// incorporated into the tree. ///小部件本身没有可变状态(它们的所有字段都必须是final)。 ///如果希望将可变状态与小部件关联,请考虑使用 /// [statefulwidget],它创建一个[状态]对象(通过 /// [statefulWidget.createState])当它被膨胀成一个元素时 ///合并到树中 /// A given widget can be included in the tree zero or more times. In particular /// a given widget can be placed in the tree multiple times. Each time a widget /// is placed in the tree, it is inflated into an [Element], which means a /// widget that is incorporated into the tree multiple times will be inflated /// multiple times. ///给定的小部件可以包含在树中零次或多次。特别地 ///给定的小部件可以多次放置在树中。每次一个小部件 ///放在树中,它被膨胀成一个[element],这意味着 ///多次合并到树中的小部件将被膨胀 ///多次。 /// The [key] property controls how one widget replaces another widget in the /// tree. If the [runtimeType] and [key] properties of the two widgets are /// [operator==], respectively, then the new widget replaces the old widget by /// updating the underlying element (i.e., by calling [Element.update] with the /// new widget). Otherwise, the old element is removed from the tree, the new /// widget is inflated into an element, and the new element is inserted into the /// tree. ///[key]属性控制一个小部件如何替换 ///树。如果两个小部件的[RuntimeType]和[Key]属性是 ///[operator==],然后新的小部件替换旧的小部件 ///更新底层元素(即,使用 ///new widget)。否则,将从树中删除旧元素 ///widget被扩展成一个元素,新元素被插入到 ///树。 /// See also: /// /// * [StatefulWidget] and [State], for widgets that can build differently /// several times over their lifetime. /// * [InheritedWidget], for widgets that introduce ambient state that can /// be read by descendant widgets. /// * [StatelessWidget], for widgets that always build the same way given a /// particular configuration and ambient state. /// *[statefulwidget]和[state],用于可以以不同方式构建的小部件 ///他们一生中有好几次。 ///*[InheritedWidget],对于引入环境状态的Widget ///由子代小部件读取。 ///*[无状态小部件],对于总是以相同方式构建的小部件 ///特定配置和环境状态。 @immutable abstract class Widget extends DiagnosticableTree { /// Initializes [key] for subclasses. const Widget({ this.key }); /// Controls how one widget replaces another widget in the tree. @override String toStringShort() { return key == null ? '$runtimeType' : '$runtimeType-$key'; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense; } /// Whether the `newWidget` can be used to update an [Element] that currently /// has the `oldWidget` as its configuration. static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; } }

Widget 和 Element 之间是怎样的对应关系呢?

从上源码注释也可知: Widget 和 Element 之间是一对多的关系 。实际上渲染树是由 Element 实例的节点构成的树,而作为配置文件的 Widget 可能被复用到树的多个部分,对应产生多个 Element 对象。

那么 RenderObject 又是什么?它和上述两个的关系是什么?

从源码注释写着 An object in the render tree 可以看出到 RenderObject 才是实际的渲染对象,而通过 Element 源码我们可以看出:Element 持有 RenderObject 和 Widget。

/// The configuration for this element. @override Widget get widget => _widget; Widget _widget; /// The object that manages the lifecycle of this element. @override BuildOwner get owner => _owner; BuildOwner _owner; ... /// The render object at (or below) this location in the tree. /// /// If this object is a [RenderObjectElement], the render object is the one at /// this location in the tree. Otherwise, this getter will walk down the tree /// until it finds a [RenderObjectElement]. RenderObject get renderObject { RenderObject result; void visit(Element element) { assert(result == null); // this verifies that there's only one child if (element is RenderObjectElement) result = element.renderObject; else element.visitChildren(visit); } visit(this); return result; }

再结合下面源码,可以大致总结出三者的关系是:
配置文件 Widget 生成了 Element,而后创建 RenderObject 关联到 Element 的内部 renderObject 对象上,最后 Flutter 通过 RenderObject 数据来布局和绘制。 理论上你也可以认为 RenderObject 是最终给 Flutter 的渲染数据,它保存了大小和位置等信息,Flutter 通过它去绘制出画面。

/// Creates an instance of the [RenderObject] class that this /// [RenderObjectWidget] represents, using the configuration described by this /// [RenderObjectWidget]. /// /// This method should not do anything with the children of the render object. /// That should instead be handled by the method that overrides /// [RenderObjectElement.mount] in the object rendered by this object's /// [createElement] method. See, for example, /// [SingleChildRenderObjectElement.mount]. @protected RenderObject createRenderObject(BuildContext context);

说到 RenderObject ,就不得不说 RenderBoxA render object in a 2D Cartesian coordinate system,从源码注释可以看出,它是在继承 RenderObject 基础的布局和绘制功能上,实现了“笛卡尔坐标系”:以 Top、Left 为基点,通过宽高两个轴实现布局和嵌套的。

RenderBox 避免了直接使用 RenderObject 的麻烦场景,其中 RenderBox 的布局和计算大小是在 performLayout()performResize() 这两个方法中去处理,很多时候我们更多的是选择继承 RenderBox 去实现自定义。

综合上述情况,我们知道:

  • Widget 只是显示的数据配置,所以相对而言是轻量级的存在,而 Flutter 中对 Widget 的也做了一定的优化,所以每次改变状态导致的 Widget 重构并不会有太大的问题。
  • RenderObject 就不同了,RenderObject 涉及到布局、计算、绘制等流程,要是每次都全部重新创建开销就比较大了。

所以针对是否每次都需要创建出新的 Element 和 RenderObject 对象,Widget 都做了对应的判断以便于复用,比如:在 newWidgetoldWidgetruntimeTypekey 相等时会选择使用 newWidget 去更新已经存在的 Element 对象,不然就选择重新创建新的 Element。

由此可知:
Widget 重新创建,Element 树和 RenderObject 树并不会完全重新创建。

  • Flutter

    Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 Flutter 可以与现有的代码一起工作,它正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。

    39 引用 • 92 回帖 • 3 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...