注意:為了讓分析更加簡單,和邏輯清晰,我們去掉了部分原始碼和註釋,只留下了主要的程式碼和邏輯。
最近一直在研究Flutter的渲染問題,在深入探索之後發現總是繞不過三個物件分別是Widget,Element,RenderObject,那麼Flutter為什麼需要這三個物件,這個三個物件是什麼關係?有這三個物件會提高渲染效率嗎?等等這樣的問題,我將在接下來的幾篇文章中為大家找答案。
Widget概述
widget定義
先給出Widget的定義,可能和你之前理解的元件有一點區別,下面是Flutter對Widget的定義。
Describes the configuration for an [Element].
翻譯過來的大概意思就是,"對一個Element配置的描述"。
這個概念上透露了兩點細節,第一是,Widget是Element的配置描述,有人一定會問,Element是什麼呢?在下面的章節中我們將詳細介紹Element。第二是,Widget只是一個配置描述,不是真正的渲染物件,這裡可能有點繞。舉個例子,看大家能不能理解,Widget就好比是Android開發中的xml,只是描述了一些View的顏色,大小等,真正在螢幕上顯示的是View。
在Flutter中,一切都是元件。在移動端開發中元件的概念很常見,比如Android的四大元件,Flutter把元件的概念發揮到了極致,在Flutter中,手勢(GestureDetector)都是元件,下面是GestureDetector的原始碼。
class GestureDetector extends StatelessWidget {
}
abstract class StatelessWidget extends Widget {
}
複製程式碼
GestureDetector繼承自StatelessWidget,StatelessWidget是沒有狀態的元件,這個類繼承Widget,可以看出來GestureDetector也是Widget,接下來我們簡單分析一下Widget,下面是Widget的原始碼。
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
用於更新佈局資料的一些資訊,這些資訊對於後面的佈局至關重要。
總結
- 當SingleChildRenderObjectElement被SingleChildRenderObjectWidget建立成功之後,系統會呼叫SingleChildRenderObjectElement的mount(),這個方法首先呼叫super.mount(),也就是上圖的第一步。
- RenderObjectElement的mount()先建立了一個RenderObject物件,也就是第二步,建立這個物件是在Widget類中建立的。
- 第三步,就是把這個將RenderObject新增到指定的位置的渲染樹中。