flutter界面的基石 - Widget

103 阅读4分钟

Widget是什么

Widget 是 flutter 功能的抽象描述,是视图的配置信息,同样也是数据的映射,是 flutter 开发框架中最基本的概念。前端框架中常见的名词,比如视图(View)、视图控制器(View Controller)、活动(Activity)、应用(Application)、布局(Layout)等,在 flutter 中都是 Widget。事实上,flutter 的核心设计思想便是一切皆 Widget。所以,我们学习 flutter,首先得从学会使用 Widget 开始。

Widget渲染过程

我们进行App开发时,最关注的问题就是:如何结构化的组织视图数据,提供给渲染引擎从而完成页面绘制,通常情况下,不同的UI框架处理方式也不同,但都会用到视图树(View Tree)的思想。 flutter,把视图数据的组织和渲染抽象为:Widget,Element,RenderObject三个部分。他们的关系如下图所示:

Widget.PNG

Widget

Widget 是 flutter 世界里对视图的一种结构化描述,你可以把它看作是前端中的“控件”或“组件”。Widget 是控件实现的基本逻辑单位,里面存储的是有关视图渲染的配置信息,包括布局、渲染属性、事件响应信息等。flutter 将 Widget 设计成不可变的,所以当视图渲染的配置信息发生变化时,flutter 会选择重建 Widget 树的方式进行数据更新,以数据驱动 UI 构建的方式简单高效。但是,因为涉及到大量对象的销毁和重建,所以会对垃圾回收造成压力。不过,Widget 本身并不涉及实际渲染位图,所以它只是一份轻量级的数据结构,重建的成本很低。另外,由于 Widget 的不可变性,可以以较低成本进行渲染节点复用,因此在一个真实的渲染树中可能存在不同的 Widget 对应同一个渲染节点的情况,这无疑又降低了重建 UI 的成本。

Element

Element是Widget的一个实例化对象,承载视图构建的上下文数据。 flutter,可以分为三步

  • 首页通过Widget树生成对应的Element树
  • 然后创建相应的RenderObject并关联到Element.renderObject属性上
  • 最后构建成RenderObject树,从而完成最终的渲染

Element同时持有Widget和RenderObject。但真正负责最后渲染的其实是RenderObject。既然如此,为什么需要Element呢? 因为Widget具有不可变性,Element却是可变的,Element树这一层将Widget树的变化做了抽象,可以只将真正修改的部分同步到真实的RenderObject树上,最大程度降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个视图树重建。

Widget中的State

Widget有StatelessWidget和StatefulWidget两种类型。StatefulWidget应对有交互需要动态变化效果的场景,StatelessWidget用于处理静态的,无状态的试图展示。在 flutter 中,Widget 采用由父到子、自顶向下的方式进行构建,父 Widget 控制着子 Widget 的显示样式,其样式配置由父 Widget 在构建时提供。用这种方式构建出的 Widget, 有些(比如 Text、Container、Row、Column 等)在创建时,除了这些配置参数之外不依赖于任何其他信息,换句话说,它们一旦创建成功就不再关心、也不响应任何数据变化进行重绘。这样的 Widget 被称为 StatelessWidget(无状态组件)。示意图如下:

stateless.PNG

有一些Widget(比如Image、Checkbox)的展示,除了父Widget初始化时传入的静态配置之外,还需要处理用户的交互或其内部的数据变化,并体现在UI上,示意图如下:

stateful.PNG

从定义上来看,StatefulWidget好像是万能,任何场景都可以使用StatefulWidget,事实果真如此吗? 回顾前面提到的Widget更新机制如果我们的根布局是一个StatefulWidget,其State中每更新一次UI,都将是一整个页面所有Widget的销毁和重建。虽然flutter内部通过Element层可以最大程度的降低对真实渲染视图的修改。但大量的Widget对象的销毁和重建是无法避免的。所以,我们在开发过程中。要做到正确评估你的试图呈现需求,避免无谓的StatefulWidget的使用。

总结

命令式编程强调精确控制过程细节;而声明式编程强调通过意图输出结果整体。对应到 flutter 中,意图是绑定了组件状态的 State,结果则是重新渲染后的组件。在 Widget 的生命周期内,应用到 State 中的任何更改都将强制 Widget 重新构建。其中,对于组件完成创建后就无需变更的场景,状态的绑定是可选项。这里“可选”就区分出了 Widget 的两种类型,即:StatelessWidget 不带绑定状态,而 StatefulWidget 带绑定状态。当你所要构建的用户界面不随任何状态信息的变化而变化时,需要选择使用 StatelessWidget,反之则选用 StatefulWidget。前者一般用于静态内容的展示,而后者则用于存在交互反馈的内容呈现中。