1. 前言
在本系列文章的前面三篇文章中,已经分别介绍Flutter的三种核心元素:Widget,Element,和RenderObject。并介绍了它们之间的关系。在这篇文章中,将在前面文章的基础上,对Flutter中非常出名的概念“Widget,Element,RenderObject树”进行剖析,以此加深读者对Flutter Framework的理解。
2. Widget,Element,RenderObject树的概念和作用
学习Flutter的时候经常会遇到一个概念:Widget,Element,RenderObject树。那什么是Widget,Element,RenderObject树呢?Flutter中的Widget,Element,RenderObject树指的是:Widget Tree, Element Tree, RenderObject Tree。从前面几篇文章可以知道,它们的作用分别如下:
- Widget Tree
Widget Tree是整个UI界面的配置,Flutter开发者通过Widget Tree告诉Framework想要绘制的UI界面是什么样的,这棵树是我们主要打交道的对象。 - Element Tree
Element Tree是通过Widget Tree生成的,其主要作用是维护UI元素的树形结构,并将Widget和RenderObject关联到树上。 - RenderObject Tree
RenderObject Tree也是通过Widget Tree生成的,其主要作用是负责界面的绘制和布局,是属于底层系统,Flutter开发者一般不需要直接操作该树。
为了加深对这Widget,Element,RenderObject树的理解,下面将使用一个例子进行讲解。
3. Widget,Element,RenderObject树示例
示例代码如下:
class TreeTest extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Text("tree test", textDirection: TextDirection.ltr)
);
}
}
该示例很简单,在页面中间显示一个Text,文案为tree test。其运行结果如下:
其代码对应的Widget Tree,Element Tree,RenderObject Tree的示意图如下:
结合本系列文章的前面三篇:Flutter框架分析(二)–Widget,Flutter框架分析(三)–Element,Flutter框架分析(四)-RenderObject。由上图可以看出:
- Widget Tree中一个Widget,对应Element Tree中一个Element。
- Element Tree中的Element,未必有对应RenderObject,例如StatelessElement所属的ComponentElement,就没有对应的RenderObject,其是用于组合更基础的Element。
- Widget Tree的根节点是RenderObjectToWidgetAdapter,其对应的Element是RenderObjectToWidgetElement,这是Element Tree的根节点,对应的RenderObject是RenderView,这是RenderObject Tree的根节点。
在上图中一个值得注意的地方是Text,从图中可以看到它有个子Widget:RichText,但是我们在示例中只定义了一个Text组件,这是怎么回事呢?我们可以追踪下源码。
Text的定义如下:
class Text extends StatelessWidget
因此它只是一个用于组合其他更基础的Widget,真正控制文案属性的是在build函数中定义的RichText,其源码如下:
@override
Widget build(BuildContext context) {
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
TextStyle effectiveTextStyle = style;
if (style == null || style.inherit)
effectiveTextStyle = defaultTextStyle.style.merge(style);
if (MediaQuery.boldTextOverride(context))
effectiveTextStyle = effectiveTextStyle.merge(const TextStyle(fontWeight: FontWeight.bold));
Widget result = RichText(
textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null.
locale: locale, // RichText uses Localizations.localeOf to obtain a default if this is null
softWrap: softWrap ?? defaultTextStyle.softWrap,
overflow: overflow ?? defaultTextStyle.overflow,
textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
maxLines: maxLines ?? defaultTextStyle.maxLines,
strutStyle: strutStyle,
textWidthBasis: textWidthBasis ?? defaultTextStyle.textWidthBasis,
textHeightBehavior: textHeightBehavior ?? defaultTextStyle.textHeightBehavior ?? DefaultTextHeightBehavior.of(context),
applyTextScaleFactorToWidgetSpan: _applyTextScaleFactorToWidgetSpan,
text: TextSpan(
style: effectiveTextStyle,
text: data,
children: textSpan != null ? <InlineSpan>[textSpan] : null,
),
);
if (semanticsLabel != null) {
result = Semantics(
textDirection: textDirection,
label: semanticsLabel,
child: ExcludeSemantics(
child: result,
),
);
}
return result;
}
所以在Widget Tree中可以看到Text的child是RichText。
4. Widget和Element的对应关系
上文已经说过,Widget Tree中的Widget和Element Tree中的Element关系是一一对应的,但是我们在Flutter框架分析(二)–Widget一文中曾经说过,一个Widget对象可以对应多个Element对象。那这不是互相矛盾了吗?其实不是的,一个Widget对象并不等同于一个Widget Tree中的Widget对象,因为在Widget Tree中还有个位置属性(Slot)。一个Widget对象加上其在Widget Tree中的位置,才相对于一个Widget Tree中的Widget对象。
以下是一个Widget对象对应多个Element对象的一个示例:
class SameWidgetMultiElementWidget1 extends StatefulWidget {
@override
_SameWidgetMultiElementWidgetState createState() => new _SameWidgetMultiElementWidgetState();
}
class _SameWidgetMultiElementWidgetState extends State<SameWidgetMultiElementWidget1> {
int count = 0;
@override
Widget build(BuildContext context) {
Text testText = Text("multi element");
return Column(
children: <Widget>[
testText,
testText,
],
);
}
}
其对应的Widget Tree和Element Tree如下:
在上面示例中,左边是Widget Tree,右边是Element Tree。在Widget Tree中,Column有两个子节点,但是这两个子节点使用的是同一个Text对象,因此这个Text对象出现在两个不同的位置(Slot),此时在Element Tree会生成两个不同的StatelessElement对象,即一个Text对象对应两个StatelessElement对象。
注意该Widget Tree和Element Tree未包含根节点。
5. 小结
本文主要介绍了Flutter中的常见概念:Widget Tree, Element Tree, RenderObject Tree。并通过一个例子,讲解了这三棵树之间的关系。本文的重点如下:
- Widget Tree中一个Widget,对应Element Tree中一个Element,但是一个Widget可能对应多个Element,因为一个Widget和它在Widget Tree中的位置一起才对应Widget Tree中的一个Widget。
- Element Tree中的Element,未必有对应RenderObject,例如StatelessElement所属的ComponentElement,就没有对应的RenderObject,其是用于组合更基础的Element。
- Widget Tree的根节点是RenderObjectToWidgetAdapter,其对应的Element是RenderObjectToWidgetElement,这是Element Tree的根节点,对应的RenderObject是RenderView,这是RenderObject Tree的根节点。
6. 参考文档
Flutter architectural overview