ARTS Share: Flutter Widget再探究

952 阅读7分钟

接着之前的分析,继续研究 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 子类来对应。

Reference