文章目录
一、概述
Widget 是我们构建页面UI的基本元素,如设置间距用的Padding、行排列用的Row、流式布局用的 Wrap。Widget内的所有属性都是 final 类型,它是一个配置文件,是 Element 的描述性信息。Widget 的作用类似于 Android Native 开发中的 xml 角色。
Widget 的4种类型:
- StatelessWidget
- StatefulWidget
- ProxyWidget
- RenderObjectWidget
它们之间的继承关系:
下面我们分别介绍一下这些类型的特点。
二、Widget
2.1 StatelessWidget
StatelessWidget 表示一个无状态的Widget,无状态是指没有 State 对象的 Widget,只需重写 build 方法就可以显示 UI。一般用来显示没有业务逻辑的区域,如常用的 Container、 Text、Builder 等就是 StatelessWidget。
class MyStatelessWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Text("StatelessWidget");
}
}
我们知道每一个 Widget 都对有一个对应的 Element,StatelessWidget 对应的 Element 是 StatelessElement。
StatelessWidget.createElement()
会在Element.inflateWidget()
中被调用。如下图所示:
abstract class StatelessWidget extends Widget {
const StatelessWidget({ Key? key }) : super(key: key);
StatelessElement createElement() => StatelessElement(this); //这里将Widget作为参数传递给 StatelessElement。
Widget build(BuildContext context); //该方法在StatelessElement.build()中被触发。
}
class StatelessElement extends ComponentElement {
StatelessElement(StatelessWidget widget) : super(widget);
Widget build() => widget.build(this); //当Element.build()被调用时,实际是调用Widget.build()方法来构建Widget。
}
小结:
- StatelessWidget 是无状态的 Widget,只需要创建对应的 StatelessElement 即可。
2.2 StatefulWidget
StatelessWidget 表示一个有状态的Widget,有状态是指 Widget 内会创建 State 对象,在 State 中可以根据运行时的不同数据,显示不同 UI 。常用的 Scaffold、Checkbox、AppBar 等就是 StatefulWidget。
class MyStatefulWidget extends StatefulWidget {
State<StatefulWidget> createState() {
return _MyWidgetState();
}
}
class _MyWidgetState extends State<MyStatefulWidget> {
Widget build(BuildContext context) {
return Text("StatefulWidget");
}
}
- 前面我们提到,Widget 内的所有属性都是 final 类型(不可变),因为 Widget 是 Element 的一个配置信息。但在实际的业务场景中,肯定会存在配置数据可变的场景,因此 StatefulWidget 通过在内部抽象出一个 State 的概念用来存放可变参数(StatefulWidget 本身保持不可变),实现可变因素与不可变因素的拆分。
- StatefulWidget 对应的 Element 是 StatefulElement。
- Widget 最终都会调用 build() 方法,在 StatefulWidget 中,会通过 State.build() 对 StatefulWidget.build() 进行代理来实现页面渲染。
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key? key }) : super(key: key);
StatefulElement createElement() => StatefulElement(this);
State createState();
}
class StatefulElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatefulElement(StatefulWidget widget)
: _state = widget.createState(), //StatefulElement构造方法中创建State.
super(widget) {
state._element = this;
state._widget = widget;
}
Widget build() => state.build(this); //将Widget.build()方法通过State.build()进行代理。
}
小结:
- StatefulWidget 是有状态的 Widget,需要创建对应的 StatefulElement 和 State。
- StatefulWidget 和 StatelessWidget 都是组合型的 Widget,都是 ComponentElement 的子类,并且不参与绘制等过程。所以在它们的 Widget 的代码中,没有任何的绘制相关的信息。
2.3 ProxyWidget
代理类型 Widget,并不用于直接显示 UI,这个类型的 Widget 一般作为功能实现的支持,比如我们熟知的 MediaQuery 节点。常见的 ProxyWidget 有 InheritedWidget、hook 父节点数据的 ParentDataWidget 等。
abstract class ProxyWidget extends Widget {
const ProxyWidget({ Key? key, required this.child }) : super(key: key);
final Widget child; //只有一个子Widget.
}
ProxyWidget 内只有一个 child Widget,这个
child
字段就是显示的内容。
2.4 RenderObjectWidget
RenderObjectWidget 是渲染类型的 Widget,负责为 Element 创建用于渲染的 RenderObject,创建的 RenderObject 中就包含了布局、绘制等流程。系统提供的组件中多数都是渲染类型的。比如:Padding、Opacity、Row 等。这种类型的 Widget 重写比较复杂,需要指定渲染过程。
下面以 Padding 组件为例来看大概的流程。
Padding -> SingleChildRenderObjectWidget -> RenderObjectWidget
class Padding extends SingleChildRenderObjectWidget {
const Padding({ Key? key, required this.padding, Widget? child, }) : assert(padding != null),
super(key: key, child: child);
/// The amount of space by which to inset the child.
final EdgeInsetsGeometry padding;
// 创建 RenderObject 对象。
RenderPadding createRenderObject(BuildContext context) {
return RenderPadding(
padding: padding,
textDirection: Directionality.maybeOf(context),
);
}
// SingleChildRenderObjectWidget类中
// 创建Widget对应的Element.
SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}
abstract class RenderObjectWidget extends Widget {
// 构造
const RenderObjectWidget({ Key? key }) : super(key: key);
// 为每个Widget创建对应的Element.
RenderObjectElement createElement();
// 创建RenderObject,并放入到RenderObject树种.
RenderObject createRenderObject(BuildContext context);
// 更新RenderObject.
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
// 将RenderObject从RenderObject树中移除时,会调用该方法。可以在这里做一些资源释放。
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
下面我们看下与 Padding 对应的 RenderObject (即 RenderPadding)。RenderPadding 中主要看三个方法:
- RenderObject.performLayout():测量当前控件的尺寸信息。
- RenderObject.layout() :内部会调用 RenderObject.performLayout() 方法进行递归测量。
- RenderObject.paint() :绘制操作。
class RenderPadding extends RenderShiftedBox {
// 布局逻辑
void performLayout() {
final BoxConstraints constraints = this.constraints;
// ...
if (child == null) {
// 1.没有child时,返回当前RenderObject的尺寸。
return;
}
// 2.有child时,先递归测量child的尺寸,最后才确定当前RenderObject的尺寸。
final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding!);
// 2.1 递归测量子RenderObject的尺寸,传入当前可用的尺寸.
child!.layout(innerConstraints, parentUsesSize: true);
// ...
// 2.2 确定当前RenderObject的尺寸.
// ...
}
// RenderShiftedBox类中。
// 绘制逻辑
void paint(PaintingContext context, Offset offset) {
if (child != null) {
final BoxParentData childParentData = child!.parentData! as BoxParentData;
context.paintChild(child!, childParentData.offset + offset);
}
}
}
// RenderObject类
void layout(Constraints constraints, { bool parentUsesSize = false }) {
if (sizedByParent) {
performResize();
}
// 调用当前RenderObject的performLayout()方法。
performLayout();
markNeedsSemanticsUpdate();
_needsLayout = false;
markNeedsPaint();
}
小结:
- RenderObjectWidget 是实际的渲染 Widget,所以需要创建对应的 StatefulElement 和 RenderObject (实际进行渲染的对象)。
- RenderObject.performLayout() 方法对 RenderObject 树进行递归测量对应 RenderObject 的尺寸信息(先测量子节点,再测量父节点,这与Android中的 measure 流程相同)。
- RenderObject.paint() 方法会执行渲染逻辑。
三、渲染前后 Widget 树的差异
代码层面调用某个 Widget 时,渲染出来的 Widget 树不一定只有当前这一个 Widget,因为Widget 里面的属性可能也是通过其他 Widget 的组合来实现的。
下面以设置了 Color 属性的 Container 为例来说明:
class MyPage extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
color: Colors.red,
child: Row(
children: [
Image.network(
"http url",
width: 500,
height: 500,
),
Text("My page.")
],
),
);
}
}
class Container extends StatelessWidget {
Widget build(BuildContext context) {
//...
// 如果颜色不为空,则为颜色设置一个ColoredBox,ColoredBox是一个RenderObjectWidget。
if (color != null) {
// 将 Text类型的Widget作为ColoredBox的child节点。
current = ColoredBox(color: color!, child: current);
}
return current!;
}
}
class ColoredBox extends SingleChildRenderObjectWidget {}
上述的 Container 设置颜色后,Widget 树的结构如下图红框所示,会多一个 ColoredBox。
MyPage 渲染前后的 Widget 树如下图所示:
渲染前 | 渲染后 |
---|---|
四、小结
通过上述几类 Widget 的组合,我们可以实现任何 UI 的展示。
几种 Widget 创建的对象:
创建的对象 | StatelessWidget | StatefulWidget | ProxyWidget | RenderObjectWidget |
---|---|---|---|---|
创建Element | Y | Y | Y | Y |
创建State | / | Y | / | / |
创建RenderObject | / | / | / | Y |
小结:
- 常见的问类 Widget:
StatelessWidget、StatefulWidget、ProxyWidget、RenderObjectWidget
,各个 Widget 内创建的对象如上表所示。 - 显示 UI 时 ,Widget 的使用原则。
- 优先使用 RenderObjectWidget 类型的 Widget。
- 如果 RenderObjectWidget 不能满足需求,则尝试通过 StatelessWidget、StatefulWidget 组合 Widget 来实现。
- 如果想让 Widget 提供某种功能,通过重写 ProxyWidget 来实现。如 Provider 就是通过 InheritWidget 实现的。
- 代码层面的 Widget 关系与渲染后的 Widget 树(如上图所示)不一定相同,因为 Widget 里面的属性可能也是通过其他 Widget 的组合来实现的。