Flutter渲染 Widget Element RenderObject概述(一)

注意:爲了讓分析更加簡單,和邏輯清晰,咱們去掉了部分源碼和註釋,只留下了主要的代碼和邏輯。框架

最近一直在研究Flutter的渲染問題,在深刻探索以後發現老是繞不過三個對象分別是Widget,Element,RenderObject,那麼Flutter爲何須要這三個對象,這個三個對象是什麼關係?有這三個對象會提升渲染效率嗎?等等這樣的問題,我將在接下來的幾篇文章中爲你們找答案。less

Widget概述

widget定義

先給出Widget的定義,可能和你以前理解的組件有一點區別,下面是Flutter對Widget的定義。ide

Describes the configuration for an [Element].函數

翻譯過來的大概意思就是,"對一個Element配置的描述"。佈局

這個概念上透露了兩點細節,第一是,Widget是Element的配置描述,有人必定會問,Element是什麼呢?在下面的章節中咱們將詳細介紹Element。第二是,Widget只是一個配置描述,不是真正的渲染對象,這裏可能有點繞。舉個例子,看你們能不能理解,Widget就比如是Android開發中的xml,只是描述了一些View的顏色,大小等,真正在屏幕上顯示的是View。ui

在Flutter中,一切都是組件。在移動端開發中組件的概念很常見,好比Android的四大組件,Flutter把組件的概念發揮到了極致,在Flutter中,手勢(GestureDetector)都是組件,下面是GestureDetector的源碼。this

class GestureDetector extends StatelessWidget {
}

abstract class StatelessWidget extends Widget {
}
複製代碼

GestureDetector繼承自StatelessWidget,StatelessWidget是沒有狀態的組件,這個類繼承Widget,能夠看出來GestureDetector也是Widget,接下來咱們簡單分析一下Widget,下面是Widget的源碼。spa

abstract class Widget {
  const Widget({ this.key });
  final Key key;
  
  @protected
  Element createElement();//註釋1
  
  static bool canUpdate(Widget oldWidget, Widget newWidget) {//註釋2
   return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}
複製代碼

原來Widget是一個抽象類,這個類有一個構造函數,參數是一個key,這個key有一個很重要的功能,用途是比較兩個Widget是否是同一個Widget,在註釋2就用到了這個key。而後有兩個方法,分別是createElement()和一個靜態的方法canUpdate()。翻譯

  • 註釋1

createElement()是一個抽象方法,子類必須實現這個方法,可是大部分咱們用都是系統的Widget,好比StatefulWidget和StatelessWidget,他們都默認實現了這方法,這方法也很是簡單,建立了一個Element。這裏面隱含了一很重要問題的答案,開篇咱們問了這樣的問題,這個三個對象是什麼關係?如今咱們至少知道了Widget和Element的關係了,一個Widget有一個Element對象,是經過createElement()建立的設計

  • 註釋2

canUpdate()方法很簡單,就是判斷oldWidget和newWidget是否是同一個Widget,若是他們的runtimeType和key相同,就認爲是同一個Widget。

Widget的特性

Widget是一個很重要的概念,可是Widget有一個更重重要的特性,就是Widget是immutable(不可變的)的,這是什麼意思?下面咱們講解一下,咱們拿Opacity爲例給你們講解,講解以前咱們先看一下Opacity的繼承關係。(在講源碼以前咱們先看一下Opacity的職責是什麼,Opacity是一個能讓他的孩子透明的組件,很簡單也很容易理解。)

class Opacity extends SingleChildRenderObjectWidget {
}

abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
}

abstract class RenderObjectWidget extends Widget {
}
複製代碼

從上面能夠看出來,Opacity繼承自SingleChildRenderObjectWidget,這類只包含了一個child的Widget,它繼承自RenderObjectWidget,RenderObjectWidget繼承自Widget。下面是具體分析一下Opacity,下面是是源碼。

class Opacity extends SingleChildRenderObjectWidget {
  
  const Opacity({
    Key key,
    @required this.opacity,
    Widget child,
  }) : super(key: key, child: child);
  
  final double opacity;//註釋1
  
  @override
  RenderOpacity createRenderObject(BuildContext context) {//註釋2
    return RenderOpacity(
      opacity: opacity
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
    renderObject
      ..opacity = opacity
  }
}
複製代碼
  • 註釋1

在註釋1處聲明瞭一個屬性,這屬性是final,也就除了構造函數能給這個屬性賦值以外,沒有其餘的辦法讓這個值進行改變。那咱們想改變這個值怎麼辦,惟一的辦法就是建立一個新的Opacity。

爲何這樣設計呢?先透露一下這是Flutter的核心設計哲學,在接下來的章節中咱們將詳細爲你們講解。

總結

Widget好像是Android得一個xml配置文件,不參與真正的渲染,只是告訴渲染層我長什麼樣式,而且這個對象的屬性是不能夠改變的,要想改變只能重現建立一個對象。

Element概述

Element定義

仍是老規矩,先看一下定義。

An instantiation of a [Widget] at a particular location in the tree.

翻譯過來的大概意思就是,"在Element表示一個Widget樹中特定位置的實例",下面咱們看一下Element類的源代碼。

abstract class Element extends DiagnosticableTree implements BuildContext {
   Element(Widget widget)
    : _widget = widget;//註釋1
  
  @mustCallSuper
  void mount(Element parent, dynamic newSlot) {//註釋2
  }
  
  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
  }
}
複製代碼

上面找了2個重點的方法和1個重要的屬性,其實Element的屬性和方法很是多,經過構造函數能夠看出來,一個Element持有一個Widget,下面咱們分析一下Element的建立過程。

Element建立

經過上面的Widget概述那一節咱們知道,Widget有一個抽象方法createElement(),用來建立Element的,這個方法的具體實現有不少,咱們找一個上面咱們分析過的SingleChildRenderObjectWidget,這個類很是簡單,只有一個child,下面看一這個類的源碼。

abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  const SingleChildRenderObjectWidget({ Key key, this.child }) : super(key: key);
  final Widget child;
  @override
  SingleChildRenderObjectElement createElement()//註釋1 
    => SingleChildRenderObjectElement(this); 
}
複製代碼

上面已經說過這個類的繼承關係,這個類繼承RenderObjectWidget,構造函數也很簡單,傳入一個child,重要的是在註釋1處,這個類建立一個類,是SingleChildRenderObjectElement,經過名字猜測,這必定是一個Element了,下面咱們就分析一下SingleChildRenderObjectElement類。

這裏驗證了,Weight和Element的關係,一個Widget有一個Element對象,是經過createElement()建立的。

還在分析以前,咱們先看一下SingleChildRenderObjectElement的繼承關係,下面是SingleChildRenderObjectElement的繼承關係。

class SingleChildRenderObjectElement extends RenderObjectElement {
}

abstract class RenderObjectElement extends Element {
}
複製代碼

從上面的繼承關係能夠看出來,SingleChildRenderObjectElement繼承RenderObjectElement,而RenderObjectElement是一個Element,你們是否是發現這繼承關係和SingleChildRenderObjectWidget很是像,下面是SingleChildRenderObjectElement的源碼。

mount方法調用

class SingleChildRenderObjectElement extends RenderObjectElement {
  SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);

  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);//註釋2
    _child = updateChild(_child, widget.child, null);//註釋1
  }
}
複製代碼

構造函數比較簡單,下面咱們看一下看,mount方法(到這裏有的人必定會問了,爲何上來就分析mount方法呢?等下一篇文章,咱們分析一下Flutter的啓動過程就清楚了),這個方法是當新建立的元素第一次添加到樹中時,框架會調用此函數。

  • 註釋1

    這方法很是關鍵,咱們將用一個小節專門去分析,請查看updateChild分析小節。

  • 註釋2

    註釋2處,調用了父類的mount,咱們看一下父類的mount的方法。

abstract class RenderObjectElement extends Element {
  RenderObjectElement(RenderObjectWidget widget) : super(widget);
  
  RenderObject _renderObject;//註釋1

  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this);//註釋2
    attachRenderObject(newSlot);
    _dirty = false;
  }
}
複製代碼
  • 註釋1

在註釋1處,咱們發現RenderObjectElement還持有一個對象,這對象是RenderObject,咱們好像明白了一點什麼,Element分別持有Widget和RenderObject,到這裏咱們解答了**這個三個對象是什麼關係?**的問題,一個Element包含一個RenderObject和一個Widget

  • 註釋2

重點在註釋2的地方,這裏建立了一個RenderObject,調用的是Widget的createRenderObject方法,下面咱們看一下attachRenderObject這方法,下面是attachRenderObject的源碼。

abstract class RenderObjectElement extends Element {
  	@override
    void attachRenderObject(dynamic newSlot) {
      _slot = newSlot;
      _ancestorRenderObjectElement = _findAncestorRenderObjectElement();//註釋1
      _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
      if (parentDataElement != null)
      _updateParentData(parentDataElement.widget);//註釋2
    }
}
複製代碼
  • 註釋1

註釋1正如他的名字是同樣的,找到了Element樹上的祖先Element,若是祖先不爲空,就調用insertChildRenderObject方法,這個方法的意思就是把renderObject的child替換成newSlot。

  • 註釋2

用於更新佈局數據的一些信息,這些信息對於後面的佈局相當重要。

總結

  1. 當SingleChildRenderObjectElement被SingleChildRenderObjectWidget建立成功以後,系統會調用SingleChildRenderObjectElement的mount(),這個方法首先調用super.mount(),也就是上圖的第一步。
  2. RenderObjectElement的mount()先建立了一個RenderObject對象,也就是第二步,建立這個對象是在Widget類中建立的。
  3. 第三步,就是把這個將RenderObject添加到指定的位置的渲染樹中。
相關文章
相關標籤/搜索