接着之前的分析,继续研究 Flutter 中的 Widget,这是一个比较简单的类,我们再看看简化版的代码,
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
@protected Element createElement();
@override String toStringShort() {}
@override void debugFillProperties(DiagnosticPropertiesBuilder properties) {}
@override @nonVirtual bool operator ==(Object other) => super == other;
@override @nonVirtual int get hashCode => super.hashCode;
static bool canUpdate(Widget oldWidget, Widget newWidget) {}
}
首先 Widget 有一个继承来的 runtimeType,和 key、 operator == 和 hashCode 一起都是些基本的属性和方法,查了一下文档runtimeType property - Object class - dart:core library - Dart API ,基本上类似于 Java 中的 Class 类,就是为了标示当前对象的类,在 Dart 中,所有对象都是类,没有基本类型一说。 Key 这个属性设计上作用比较重要,是用来判断 Widget 是否等价,但实际上观察来看,用的地方不多,试了几个 Widget,实际运行时检查值都是 null。operator == 和 hashCode 就不多解释了,看命名就能大体理解。
Widget 还有不重要的用于调试用的方法,主要值得关注的有两个方法:Element createElement() 和 bool canUpdate(Widget oldWidget, Widget newWidget)。
canUpdate 方法的作用是用于 Element 对象要更新 Widget 时,来判断是否可更新,Widget 是不可变对象,所以比较逻辑也比较简单,效率极高。
createElement 主要用于创建 Element 节点用,可以保证每个 Widget 都有一个对应的 Element。这个函数以及返回的类组成了 Widget 和 Element 的体系对应关系,后面会仔细拆解对应的关系。
Wigdet 体系
Widget 类本身比较简单,但是 Widget 的子类是 Flutter 中最庞杂的,理解了 Widget 体系,也就大体熟悉了 Flutter 中量最大的一个领域。
我们先从官方文档中可见的内容,来梳理一下 Widget 的子类体系,总数超过100个,我们不需要都看,先看看几个常见的分类思路和重要的就好。
- 根据子节点的数量分类:
NoChild/SingleChild/MultipleChild/DynamicChild - 根据是否有状态来分类:Stateful和Stateless。
- 是否有对应的 RedenerObject
- 是否是独立的 Widget,这个有点类似于 Android 的 View 和 ViewGroup,有些 Widget 能独立存在,比如 Text,有些只是其它 Widget 的装饰,依赖于其它 Widget,比如 Center
有点可惜的是,Widget 的分类体系不够完美,子类树也太过庞杂,有些逻辑不顺的地方,不过 GUI 系统本身就是个复杂的事情,还要考虑跨平台,难免要做些设计上的折中。
我们先来看看 Widget 的直接子类有:PreferredSizeWidget、ProxyWidget、RenderObjectWidget、StatefulWidget 和 StatelessWidget。其中 PreferredSizeWidget 特别简单,主要是被其它 Widget 当作接口来实现,Dart 中接口继承的语法类似implements PreferredSizeWidget。
根据有无状态来区分:StatefulWidget 和 StatelessWidget
然后看官方教程中也着重强调的 StatefulWidget 和 StatelessWidget,按照分类来讲这个分类很完备,但是实际情况要复杂的多,同一级别还有其它类,这个令后续设计 Widget 不是那么容易,需要考虑的因素还要看细节,不能仅仅根据是否有状态来设计子类,不过大多数简单 UI 或组合式的 Widget 里只需要考虑继承这两个类就好。
StatefulWidget 的设计里有个特殊的地方,对应的 State 对象里,有 widget 和 element 的引用,所以是双向可访问的,一般情况下自动移的 StatefulWidget 类会非常简单,纯模板的样子,只要设计对应的 State 类就好。
附上简化过的代码
abstract class StatelessWidget extends Widget {
const StatelessWidget({ Key key }) : super(key: key);
@override StatelessElement createElement() => StatelessElement(this);
@protected Widget build(BuildContext context);
}
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
@override StatefulElement createElement() => StatefulElement(this);
@protected State createState();
}
@optionalTypeArgs abstract class State<T extends StatefulWidget> with Diagnosticable {
T get widget => _widget;
T _widget;
BuildContext get context => _element;
StatefulElement _element;
bool get mounted => _element != null;
@protected @mustCallSuper void initState() {}
@mustCallSuper @protected void didUpdateWidget(covariant T oldWidget) {}
@protected @mustCallSuper void reassemble() {}
@protected void setState(VoidCallback fn) {}
@protected @mustCallSuper void deactivate() {}
@protected @mustCallSuper void dispose() {}
@protected Widget build(BuildContext context)
@protected @mustCallSuper void didChangeDependencies() { }
@override void debugFillProperties(DiagnosticPropertiesBuilder properties) {}
}
可以看出重要的是分别有对应的 StatefulElement 和 StatelessElement,然后是对应 build 方法,用来返回创建好的 Widget 类,这里有个注意点,不管是 StatelessWidget 还是 StatefulWidget 都是不可变对象,发生变化都需要重新构建,只是对于 StatefulWidget 对象,有一个关联的 State 对象,用于保存状态信息,这个 State 通常都是由开发人员根据业务/交互需要来实现,这个对象是可变的。
特殊子类树分支 ProxyWidget
在学习过程中,发现一个特殊的分支,代码没啥难度,但是设计思路和用途不是很清楚,先列出来备忘。
这个分支的 Widget 像是一个代理模式的类,都有一个子元素,这个分支有一个重要性高的子类 InheritedWidget,在后续状态管理中作用非常大。
abstract class ProxyWidget extends Widget {
const ProxyWidget({ Key key, @required this.child }) : super(key: key);
final Widget child;
}
abstract class ParentDataWidget<T extends ParentData> extends ProxyWidget {
const ParentDataWidget({ Key key, Widget child }) : super(key: key, child: child);
@override ParentDataElement<T> createElement() => ParentDataElement<T>(this);
...
}
abstract class InheritedWidget extends ProxyWidget {
const InheritedWidget({ Key key, Widget child }) : super(key: key, child: child);
@override InheritedElement createElement() => InheritedElement(this);
...
}
和 RenderObject 有关的子类分支 RenderObjectWidget
通常情况下,Widget 树和 Element 树是完整的,和代码写的组合方式一一对应,但是 RenderObject 的类就不一定了,只有与渲染有关系的 Element 才会有对应的 RenderObject,所以 RenderObject 树的层次会简单很多,从类的设计上而言,只有 RenderObjectWidget 和 RenderObjectElement 的子类才会需要考虑 RenderObject 的问题。
RenderObjectWidget 里提供了管理 RenderObject 对象的方法:createRenderObject、updateRenderObject 和 didUnmountRenderObject。
在 RenderObjectWidget 这个类的分支里,考虑到了子节点数量的分类,所以有:叶子节点 LeafRenderObjectWidget、单子节点 SingleChildRenderObjectWidget、多子节点 MultiChildRenderObjectWidget。这里又暴露一个问题,这是一个比较完备的分类,但是还有其它同级的类 ConstrainedLayoutBuilder、ListWheelViewport、RenderObjectToWidgetAdapter、SliverWithKeepAliveWidget 和 Table。都有特定的适用场合和设计目的,暂时跳过。
abstract class RenderObjectWidget extends Widget {
const RenderObjectWidget({ Key key }) : super(key: key);
@override RenderObjectElement createElement();
@protected RenderObject createRenderObject(BuildContext context);
@protected void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
@protected void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
abstract class LeafRenderObjectWidget extends RenderObjectWidget {
const LeafRenderObjectWidget({ Key key }) : super(key: key);
@override LeafRenderObjectElement createElement() => LeafRenderObjectElement(this);
}
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
const SingleChildRenderObjectWidget({ Key key, this.child }) : super(key: key);
final Widget child;
@override SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
MultiChildRenderObjectWidget({ Key key, this.children = const <Widget>[] })
final List<Widget> children;
@override MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);
}
abstract class ConstrainedLayoutBuilder<ConstraintType extends Constraints> extends RenderObjectWidget {
const ConstrainedLayoutBuilder({Key key, @required this.builder,})
@override _LayoutBuilderElement<ConstraintType> createElement() => _LayoutBuilderElement<ConstraintType>(this);
final Widget Function(BuildContext, ConstraintType) builder;
}
class ListWheelViewport extends RenderObjectWidget {
const ListWheelViewport({Key key, ...})
...
final ListWheelChildDelegate childDelegate;
@override ListWheelElement createElement() => ListWheelElement(this);
@override RenderListWheelViewport createRenderObject(BuildContext context) {}
@override void updateRenderObject(BuildContext context, RenderListWheelViewport renderObject) {}
}
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
RenderObjectToWidgetAdapter({this.child, this.container, this.debugShortDescription})
final Widget child;
final RenderObjectWithChildMixin<T> container;
final String debugShortDescription;
@override RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
@override RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;
@override void updateRenderObject(BuildContext context, RenderObject renderObject) { }
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {}
@override String toStringShort() => debugShortDescription ?? super.toStringShort();
}
abstract class SliverWithKeepAliveWidget extends RenderObjectWidget {
const SliverWithKeepAliveWidget({Key key,}) : super(key : key);
@override RenderSliverWithKeepAliveMixin createRenderObject(BuildContext context);
}
class Table extends RenderObjectWidget {
Table({Key key, this.children = const <TableRow>[],...})
final List<TableRow> children;
@override _TableElement createElement() => _TableElement(this);
@override RenderTable createRenderObject(BuildContext context) {}
@override void updateRenderObject(BuildContext context, RenderTable renderObject) {}
}
其它
在看 Widget 相关的逻辑时,发现还有一个 Slivers 的概念,Slivers 主要时和滚动有关系,针对这个功能单独做了些支持,现有的 Widget 的子类树设计的不够好,感觉妥协有点多,没有一致的分类和使用逻辑,这个必然会影响到用户自定义的 Widget 类,期待后续项目团队能做一次梳理,目前影响不大。
总结
在 Flutter 里,Widget 的子类树是最复杂,数量最多,且有不同维度的分类,有些应该是和实际需求妥协的结果,导致不少设计不够完美,需要了解细节才能用好,从官方文档中推荐的方式来看,如果需要继承子类,区分 StatelessWidget 和 StatefulWidget 就好,从这里开始构建自己的 Widget 子类。要想了解细节还得结合着 Element 和 RenderObject 来看。
本篇文章分析了所有重要的子类,可以看到这些子类大多都有对应的 Element 子类,而与之对应的 RenderObject 子类则是 RenderObjectWidget 子类来对应。