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
。