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

1. 前言

在本系列文章的前面三篇文章中,已經分別介紹Flutter的三種核心元素:WidgetElement,和RenderObject。並介紹了它們之間的關係。在這篇文章中,將在前面文章的基礎上,對Flutter中很是出名的概念「WidgetElementRenderObject樹」進行剖析,以此加深讀者對Flutter Framework的理解。markdown

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界面是什麼樣的,這棵樹是咱們主要打交道的對象。app

  • Element Tree

Element Tree是經過Widget Tree生成的,其主要做用是維護UI元素的樹形結構,並將WidgetRenderObject關聯到樹上。框架

  • RenderObject Tree

RenderObject Tree也是經過Widget Tree生成的,其主要做用是負責界面的繪製和佈局,是屬於底層系統,Flutter開發者通常不須要直接操做該樹。 爲了加深對這WidgetElementRenderObject樹的理解,下面將使用一個例子進行講解。less

3. Widget,Element,RenderObject樹示例

示例代碼以下:ide

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的示意圖以下:oop

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組件,這是怎麼回事呢?咱們能夠追蹤下源碼。post

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

相關文章
相關標籤/搜索