Flutter框架分析(五)-Widget,Element,RenderObject树

·  阅读 841

1. 前言

在本系列文章的前面三篇文章中,已经分别介绍Flutter的三种核心元素:WidgetElement,和RenderObject。并介绍了它们之间的关系。在这篇文章中,将在前面文章的基础上,对Flutter中非常出名的概念“WidgetElementRenderObject树”进行剖析,以此加深读者对Flutter Framework的理解。

2. Widget,Element,RenderObject树的概念和作用

学习Flutter的时候经常会遇到一个概念:WidgetElementRenderObject树。那什么是WidgetElementRenderObject树呢?Flutter中的WidgetElementRenderObject树指的是:Widget Tree, Element Tree, RenderObject Tree。从前面几篇文章可以知道,它们的作用分别如下:

  • Widget Tree Widget Tree是整个UI界面的配置,Flutter开发者通过Widget Tree告诉Framework想要绘制的UI界面是什么样的,这棵树是我们主要打交道的对象。
  • Element Tree Element Tree是通过Widget Tree生成的,其主要作用是维护UI元素的树形结构,并将WidgetRenderObject关联到树上。
  • RenderObject Tree RenderObject Tree也是通过Widget Tree生成的,其主要作用是负责界面的绘制和布局,是属于底层系统,Flutter开发者一般不需要直接操作该树。 为了加深对这WidgetElementRenderObject树的理解,下面将使用一个例子进行讲解。

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。其运行结果如下:

image.png

其代码对应的Widget TreeElement TreeRenderObject Tree的示意图如下:

image.png

结合本系列文章的前面三篇:Flutter框架分析(二)--WidgetFlutter框架分析(三)--ElementFlutter框架分析(四)-RenderObject。由上图可以看出:

  1. Widget Tree中一个Widget,对应Element Tree中一个Element
  2. Element Tree中的Element,未必有对应RenderObject,例如StatelessElement所属的ComponentElement,就没有对应的RenderObject,其是用于组合更基础的Element
  3. Widget Tree的根节点是RenderObjectToWidgetAdapter,其对应的ElementRenderObjectToWidgetElement,这是Element Tree的根节点,对应的RenderObjectRenderView,这是RenderObject Tree的根节点。

在上图中一个值得注意的地方是Text,从图中可以看到它有个子WidgetRichText,但是我们在示例中只定义了一个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中的WidgetElement 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 TreeElement Tree如下:

image.png

在上面示例中,左边是Widget Tree,右边是Element Tree。在Widget Tree中,Column有两个子节点,但是这两个子节点使用的是同一个Text对象,因此这个Text对象出现在两个不同的位置(Slot),此时在Element Tree会生成两个不同的StatelessElement对象,即一个Text对象对应两个StatelessElement对象。

注意该Widget TreeElement Tree未包含根节点。

5. 小结

本文主要介绍了Flutter中的常见概念:Widget Tree, Element Tree, RenderObject Tree。并通过一个例子,讲解了这三棵树之间的关系。本文的重点如下:

  1. Widget Tree中一个Widget,对应Element Tree中一个Element,但是一个Widget可能对应多个Element,因为一个Widget和它在Widget Tree中的位置一起才对应Widget Tree中的一个Widget
  2. Element Tree中的Element,未必有对应RenderObject,例如StatelessElement所属的ComponentElement,就没有对应的RenderObject,其是用于组合更基础的Element
  3. Widget Tree的根节点是RenderObjectToWidgetAdapter,其对应的ElementRenderObjectToWidgetElement,这是Element Tree的根节点,对应的RenderObjectRenderView,这是RenderObject Tree的根节点。

6. 参考文档

Flutter architectural overview

7. 相关文章

Flutter框架分析(一)--架构总览
Flutter框架分析(二)-- Widget
Flutter框架分析(三)-- Element
Flutter框架分析(四)-RenderObject
Flutter框架分析(六)-Constraint
Flutter框架分析(七)-relayoutBoundary
Flutter框架分析(八)-Platform Channel
Flutter框架分析- Parent Data
Flutter框架分析 -InheritedWidget

分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改