Everything is a widget
在flutter中,我们接触最多的东西就是widget,几乎所有的对象都是一个Widget,它不仅可以表示UI元素,也可以表示一些功能性的组件,如用户手势检测的GestureDetector等。
1 | /// Widgets are the central class hierarchy in the Flutter framework. A widget |
在Widget类定义处有这么一段注释,说明了widget的作用:Widget是一个不可变的、用于描述UI配置信息、配置Element。
而Element主要职责是根据UI的变化来维护Element Tree,包括节点的插入、更新、删除等、还是Widget和RenderObject之间的协调者。
Widget特点
- 声明式UI:开发效率高
- 不可变性:
Widget内部成员都是不可变的,说明页面变化时Widget一定是被重新构建,Widget的固定状态代表了一帧静止的画面。所以Widget必须是轻量级的,不可能真正绘制对象,因为频繁的重构无法保证性能的稳定。 - 组合大于继承:通过将多个功能相对单一的
Widget组合起来得到复杂功能的Widget
Widget分类

如图所示,Widget按功能可以大致分为三类:
- Component Widget: 组合类Widget,这类Widget都直接或间接继承于
StatelessWidget或StatefulWidget。flutter widget在设计上遵循组合大于继承的原则,通过组合功能相对单一的Widget来得到功能更为复杂的Widget。这也是在flutter开发中直接使用到的绝大多数Widget的类型。 - Proxy Widget: 代理类Widget,本身并不涉及Widget的内部逻辑,只是为Child Widget提供一些附加的中间功能,例如
InheritedWidget,用于在Descendant Widget间传递信息、ParentDataWidget用于配置Descendant Renderer Widget的布局信息 - Renderer Widget: 渲染类Widget,是最核心的Widget累心,会直接参与后面的Layout、Paint流程,无论是Component Widget还是Proxy Widget,最终都会映射到Renderer Widget上,否则将无法被绘制到屏幕上。这三类Widget中,只有Renderer Widget有与之对应的Render Object
源码分析
1 |
|
Widget类中有三个重要的属性/方法:
- Key key:在同一父节点下,用作兄弟节点的唯一标识,用于控制当Widget更新时,对应的Element如何处理(更新或新建)。
- Element createElement():每个Widget都有一个与之对应的Element,此方法负责创建Element对象。(一个Widget可能被多个地方使用,Widget可能被多次调用,而Element一般只会创建一次,所以Widget和Element是多对一的关系)
- canUpdate:是否可以用前一帧widget生成的Element,而不是创建新的Element。默认实现是2个Widget的
runTimeType和Key都相等时,返回true,可以直接复用。
Widget分类对比
如果按照Widget是否有State进行分类,Widget可以分为StatelessWidget和StatefulWidget两种。StatelessWidget和StatefulWidget的Element都是Component,并且都不具备RenderObject。
还有其它三种直接使用较少的ParentDataWidget InheritedWidget RenderObjectWidget。
StatelessWidget
1 | abstract class StatelessWidget extends Widget { |
这就是我们经常使用的StatelessWidget的代码,看起来非常简单,在Widget的基础上实现了默认的createElement方法,返回了一个StatelessElement对象,并把自身作为参数传入;定义了一个需要我们自行实现的build方法,这个就很熟悉了,就是平时我们写布局的方法。
1 | class StatelessElement extends ComponentElement { |
简单看一下StatelessElement方法,也很简单,其中重写了父类的build方法,调用了StatelessWidget中我们定义的build方法,并将自身传入,所以我们平时用的非常多的BuildContext实际上是一个Element对象!
1 | abstract class ComponentElement extends Element {} |
ComponentElement继承自Element,而Element实现了BuildContext接口,所以我们build中接收的context参数实际上可以是一个Element对象
Element本篇就先提到一下,具体的细节之后再详解。
StatefulWidget
1 | abstract class StatefulWidget extends Widget { |
StatefulWidget和StatelessWidget的差别就在于StatefulWidget是有状态的,所以在StatefulWidget中,会要求我们实现一个createState方法,返回一个State对象。要注意的是,StatefulWidget本身还是不可变的,其可变状态存在于State中。
StatefulWidget默认的createElement方法返回的是StatefulElement对象。
1 | StatefulElement(StatefulWidget widget) |
StatefulElement比StatelessElement的实现就要复杂许多,上面的代码是去掉断言之后的构造函数。
widget.crateState()方法调用,将我们的State对象赋值给_state,由此看出State对象是保存在Element中而不是Widget中。State会一直保存在Element中,并与Element的生命周期进行同步。
将Element对象赋值给State的_element属性。
将StatefulWidget对象赋值给了State的_widget属性。
1 |
|
T get widget => _widget;,我们在State中取Widget中的参数,是通过widget属性拿到的。通过上面两段源码分析可以看出,State和Widget并不直接关联在一起,而是通过Element将双方关联在了一起。
BuildContext get context => _element;: 对flutter有些经验的都应该知道,在State中,在任何定义的函数中都有context字段,并不需要从build中向外传递。由此看出context实际上就是_element属性,而在StatefulElement构造函数中将其自身赋值给了_element属性。这里的context还是一个Element。
其余的方法大多都是让我们去实现的一些生命周期方法。大部分都是在Element中被调用。

ParentDataWidget
ParentDataWidget是一个ProxyWidget,这个Widget对我们来说看起来很陌生。其主要功能是为其他Widget提供parentData信息,提供的parentData信息最终都会落到RenderObjectWidget类型的子孙Widget上。
例如通常配合Stack使用的Positioned就是继承自ParentDataWidget,将自己的属性赋值给了对应的RenderObject.parentData,Stack使用属性布局。
InheritedWidget
用于在树上向下传递数据。通过BuildContext.dependOnInheritedWidgetOfExactType获取最近的Inherited Widget,当状态有变化时,会导致引用方rebuild。
常用于数据共享,比如Theme/ThemeData、Text/DefaultTextStyle、Slider/SliderTheme、Icon/IconTheme等都是通过InheritedWidget实现数据共享,并且flutter中的大部分状态管理框架也都是基于InheritedWidget实现的。
InheritedWidget共享的是Widget,只是这个Widget是ProxyWidget,本身不具备绘制能力。
1 | class InheritedExample extends InheritedWidget { |
如何实现共享
在所有Element中,都有一个Map<Type, InheritedElement> _inheritedWidgets;成员变量,一般情况下是空的,当父控件是InheritedWidget或者本身是InheritedWidget是才会被初始化。当父控件是InheritedWidget时,这个Map会在Element中被一级一级往下传递并合并。当我们通过context调用dependOnInheritedWidgetOfExactType时,就可以通过这个Map往上查找,从而找到上级的InheritedWidget。
1 | // class Element |
RenderObjectWidget
是真正与渲染相关的Widget,一切其他类型的Widget要渲染到屏幕上,最终都要回归到该类型的Widget上。
主要有三种类型LeafRenderObjectWidget SingleChildRenderObjectWidget MultiChildRenderObjectWidget,区别在于子元素的多少。
RenderObjectWidget需要实现createElement和createRenderObject方法,来创建Element和RenderObject。RenderObject就是真正用于绘制的类。
小结
本篇大概讲述了不同类型Widget的原理。
通过上面的分析我们大概了解到Widget本质上是UI的配置信息,还有一个Element对象与之对应。
我们平时经常见到的BuildContext,就是一个Element。