相信我们在Flutter开发过程中接触最多的无疑就是Widget了,通过Widget我们可以实现诸多功能:
按照官方的说法“Widget是用于描述Element的配置信息”,如何去理解这句话,下面我们具体分析:
通过源码查看,我们发现Widget是一个抽象类,直接或者间接继承自Widget的类达到六百多个,下面整合这些Widget信息对Widget做一个简单的分类,直接继承自Widget的类只有四个(还有一个PreferredSizeWidget,在我们实际开发者用得比较少),我们就以这四个作为入口:
上图所列出来的都是直接或者间接继承自Widget的抽象类,实际开发中用到的基本上都是继承自这些抽象类,总体来说这些类大致分为三部分:
Components Widget
组合类Widget,这类Widget直接或者间接继承自StatelessWidget、StatefulWidget。
Flutter遵循组合大于继承的原则,通过组合相对单一的Widget可以得到功能复杂的Widget,我们平常所使用的的各种Widget,比如:Container,Text,Scaffold等,都属于这类Widget;
Renderer Widget
渲染类Widget,是最核心的Widget类型,会直接参与Flutter UI界面的布局,绘制流程。其实Components Widget最终也是由这类Widget组合而成。
无论是Component Widget还是Proxy Widget最终都会映射到Renderer Widget上,否则将无法绘制到屏幕上。
这三类Widget只有Renderer Widget有与之一一对应的Render Object。
Proxy WIdget
代理类WIdget,如其名,它并不设计Widget内部逻辑,只是为Child Widget提供一些附件的中间功能。往往是将Widget附加到当前Proxy Widget的Child属性上,实现信息传递与共享。
如;InheritedWidget用于从父Widget到子Widget信息传递,ParentDataWidget用于配置布局信息传递。
Widget
是所有Widget的基类
@immutable
abstract class Widget extends DiagnosticableTree {
/// Initializes [key] for subclasses.
const Widget({ this.key });
final Key? key;
@protected
@factory
Element createElement();
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
Widget是一个immutable类,继承自DiagnosticableTree
,DiagnosticableTree
这个类,主要是用于在调试的时候获取子类的各种属性和children信息,这里暂且不管它。
通过源码我们看到它的三个核心部分(属性和方法)
Key
在同一父节点下,用作兄弟节点间的唯一标识,主要用于(结合下面canUpdate
方法)控制当 Widget 更新时,对应的 Element 如何处理 (是更新还是新建);
Element createElement()
每个Widget
都有一个与之对应的Element
,由该方法负责创建,createElement
可以理解为设计模式中的工厂方法,具体的Element
类型由对应的Widget
子类负责创建;
bool canUpdate(…)
canUpdate
其实我们在深入理解Key一文中已经见识过了。主要是判断是否可以用 new widget 修改前一帧用 old widget 生成的 Element,而不是创建新的 Element,Widget
类的默认实现为:2个Widget
的runtimeType
与key
都相等时,返回true
,即可以直接更新 (key 为 null 时,认为相等)。
abstract class StatelessWidget extends Widget {
/// Initializes [key] for subclasses.
const StatelessWidget({ Key? key }) : super(key: key);
@override
StatelessElement createElement() => StatelessElement(this);
@protected
Widget build(BuildContext context);
}
无状态Component Widget,由build构建组合Widget层次结构,在它生命周期内不可变。
方法解析
StatelessElement createElement()
一般情况下子类不需要重写该方法,子类对应的Element也是StatelessElement是ComponentElement的一种
Widget build(BuildContext context)
核心方法,构建该组合式 Widget 的 UI 层级结构及样式信息。该方法通常只在一下三种情况下调用:
Parent Widget
修改了其配置信息;Inherited Widget
发生变化时。当Parent Widget
或 依赖的Inherited Widget
频繁变化时,build
方法也会频繁被调用。因此,提升build
方法的性能就显得十分重要,Flutter 官方给出了几点建议:
Single Child Widget
,没必要通过组合Row
、Column
、Padding
、SizedBox
等复杂的 Widget 达到某种布局的目标,或许通过简单的Align
、CustomSingleChildLayout
即可实现。又或者,为了实现某种复杂精细的 UI 效果,不一定要通过组合多个Container
,再附加Decoration
来实现,通过 CustomPaint
自定义或许是更好的选择;Stateless Widget
重构成Stateful Widget
,以便可以使用Stateful Widget
中一些特定的优化手法,如:缓存sub trees
的公共部分,并在改变树结构时使用GlobalKey;Inherited Widget
,导致频繁 rebuilt,可以将真正依赖Inherited Widget
的部分提取出来,封装成更小的独立 Widget,并尽量将该独立 Widget 推向树的叶子节点,以便减小 rebuilt 时受影响的范围。abstract class StatefulWidget extends Widget {
/// Initializes [key] for subclasses.
const StatefulWidget({ Key? key }) : super(key: key);
@override
StatefulElement createElement() => StatefulElement(this);
@protected
@factory
State createState(); // ignore: no_logic_in_create_state, this is the original sin
}
有状态Component Widget,它也是immutable类,本身是不可变的。其可变的状态存在于State
中。
方法解析
StatefulElement createElement()
StatefulWidget
对应的Element为StatefulElement
,一般情况下也不需要重写该方法,所以子类对于的Element也是StatefulElement
是ComponentElement的一种。
State createState()
创建对应的 State,该方法在StatefulElement
的构造方法中被调用。可以简单地理解为当Stateful Widget
被添加到 Widget Tree 时会调用该方法。如果是更新Element并不会调用该方法。
class StatefulElement extends ComponentElement {
// 构造方法中调用createState
StatefulElement(StatefulWidget widget)
: state = widget.createState(),
super(widget) {
}
}
实际上是Stateful Widget
对应的Stateful Element
被添加到 Element Tree 时,伴随Stateful Element
的初始化,createState
方法被调用。一个 Widget 实例可以对应多个 Element 实例 (也就是同一份配置信息 (Widget) 可以在 Element Tree 上不同位置配置多个 Element 节点),因此,createState
方法在Stateful Widget
生命周期内可能会被调用多次。
另外,需要注意的是配有GlobalKey
的 Widget 对应的 Element 在整个 Element Tree 中只有一个实例。
State
是用于描述StatefulWidget
的业务逻辑和内部状态。创建时机上面已经讲过,这里需要注意的是如果从树中移除一个StatefulWidget并稍后再次插入到树中,那么framework将会再次调用StatefulWidget.createState来创建一个新的State对象,如果不移除只是update的话是不会再次调用createState的。
其生命周期:
StatefulWidget.createState
创建一个State
对象 。State
对象与BuildContext
相关联。这种关联是永久性的:State
对象永远不会改变它的 BuildContext
。但是,BuildContext
本身可以与其子树一起在树周围移动。此时State
对象被认为是mount
。State.initState
,子类可以重写该方法执行相关的初始化操作 (此时可以引用context
、widget
属性);Inherited Widget
) 状态发生变化时也会被调用,子类很少需要重写该方法,除非有非常耗时不宜在build中进行的操作,因为在依赖有变化时build方法也会被调用;build
方法此后可能会被多次调用,在状态变化时 State 可通过setState
方法来触发其子树的重建;element tree
、renderobject tree
、layer tree
已构建完成,完整的 UI 应该已呈现出来。此后因为变化,element tree
中parent element
可能会对树上该位置的节点用新配置 (Widget) 进行重建,当新老配置 (oldWidget、newWidget)具有相同的runtimeType
&&「key」时,framework 会用 newWidget 替换 oldWidget,并触发一系列的更新操作 (在子树上递归进行)。同时,State.didUpdateWidget
方法被调用,子类重写该方法去响应 Widget 的变化;runtimeType
||key
不相等时)。此时会调用State.deactivate
方法,由于被移除的节点可能会被重新插入树中某个新的位置上,故子类重写该方法以清理与节点位置相关的信息 (如:该 State 对其他 element 的引用)、同时,不应在该方法中做资源清理;State.build
方法被再次调用;State.dispose
方法被执行,State 生命周期随之结束,此后再调用State.setState
方法将报错。子类重写该方法以释放任何占用的资源。void setState(VoidCallback fn) {
_element!.markNeedsBuild();
}
setState
方法很简单,去掉冗余的assert信息,其实只有一行代码就是调用_element.markNeedsBuild()
方法。_element.markNeedsBuild
方法后面Element类分析的时候再讲解。
setState
方法有几个点值得关注(通过断言assert分析得出):
State.dispose
之后,不能再调用setStatesetState
setState
方法的回调函数(fn)不能是异步(返回值为Future)。setState
之所以能更新UI,是因为内部调用了_element.markNeedsBuild()
,间接调用了onBuildScheduled
。InheritedWidget
在之前的文章深入理解数据共享InheritedWidget已经讲解过,这里不再重复。
RenderObjectWidget
为RenderObjectlements
提供配置信息,通过包装RenderObjects
提供实际渲染需要的数据。一切其他类型的Widget
知道它要渲染到屏幕上,最终都要回归到该类型的Widget上。
abstract class RenderObjectWidget extends Widget {
const RenderObjectWidget({ Key? key }) : super(key: key);
@override
@factory
RenderObjectElement createElement();
@protected
@factory
RenderObject createRenderObject(BuildContext context);
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
核心方法就只有四个:
createElement()
RenderObjectWidget
对应的Element为RenderObjectElement
,由于RenderObjectElement
也是抽象类,所以子类需要重写该方法。
createRenderObject(BuildContext context)
核心方法,创建 Render Widget 对应的 Render Object,同样子类需要重写该方法。该方法在对应的 Element 被挂载到树上时调用(Element.mount
),即在 Element 挂载过程中同步构建了Render Tree
updateRenderObject(BuildContext context, covariant RenderObject renderObject)
核心方法,在 Widget 更新后,修改对应的 Render Object。该方法在首次 build 以及需要更新 Widget 时都会调用;
didUnmountRenderObject(covariant RenderObject renderObject)
对应的Render Object
从Render Tree
上移除时调用该方法。
好了,到了这里Widget介绍总算结束,这里做个总结:
Component Widget
、Proxy Widget
以及Renderer Widget
State
对象与BuildContext
相关联。这种关联是永久性的:State
对象永远不会改变它的 BuildContext
。但是,BuildContext
本身可以与其子树一起在树周围移动。此时State
对象被认为是mount
。Renderer Widget
才会参与最终的 UI 生成过程(Layout、Paint),只有该类型的 Widget 才有与之对应的Render Object
,同样由其提供创建方法(createRenderObject
)。