注意:為了讓分析更加簡(jiǎn)單,和邏輯清晰,我們?nèi)サ袅?strong>部分源碼和注釋,只留下了主要的代碼和邏輯。
最近一直在研究Flutter的渲染問(wèn)題,在深入探索之后發(fā)現(xiàn)總是繞不過(guò)三個(gè)對(duì)象分別是Widget,Element,RenderObject,那么Flutter為什么需要這三個(gè)對(duì)象,這個(gè)三個(gè)對(duì)象是什么關(guān)系?有這三個(gè)對(duì)象會(huì)提高渲染效率嗎?等等這樣的問(wèn)題,我將在接下來(lái)的幾篇文章中為大家找答案。
Widget概述
widget定義
先給出Widget的定義,可能和你之前理解的組件有一點(diǎn)區(qū)別,下面是Flutter對(duì)Widget的定義。
Describes the configuration for an [Element].
翻譯過(guò)來(lái)的大概意思就是,"對(duì)一個(gè)Element配置的描述"。
這個(gè)概念上透露了兩點(diǎn)細(xì)節(jié),第一是,Widget是Element的配置描述,有人一定會(huì)問(wèn),Element是什么呢?在下面的章節(jié)中我們將詳細(xì)介紹Element。第二是,Widget只是一個(gè)配置描述,不是真正的渲染對(duì)象,這里可能有點(diǎn)繞。舉個(gè)例子,看大家能不能理解,Widget就好比是Android開(kāi)發(fā)中的xml,只是描述了一些View的顏色,大小等,真正在屏幕上顯示的是View。
在Flutter中,一切都是組件。在移動(dòng)端開(kāi)發(fā)中組件的概念很常見(jiàn),比如Android的四大組件,F(xiàn)lutter把組件的概念發(fā)揮到了極致,在Flutter中,手勢(shì)(GestureDetector)都是組件,下面是GestureDetector的源碼。
class GestureDetector extends StatelessWidget {
}
abstract class StatelessWidget extends Widget {
}
GestureDetector繼承自StatelessWidget,StatelessWidget是沒(méi)有狀態(tài)的組件,這個(gè)類(lèi)繼承Widget,可以看出來(lái)GestureDetector也是Widget,接下來(lái)我們簡(jiǎn)單分析一下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;
}
}
原來(lái)Widget是一個(gè)抽象類(lèi),這個(gè)類(lèi)有一個(gè)構(gòu)造函數(shù),參數(shù)是一個(gè)key,這個(gè)key有一個(gè)很重要的功能,用途是比較兩個(gè)Widget是不是同一個(gè)Widget,在注釋2就用到了這個(gè)key。然后有兩個(gè)方法,分別是createElement()和一個(gè)靜態(tài)的方法canUpdate()。
- 注釋1
createElement()是一個(gè)抽象方法,子類(lèi)必須實(shí)現(xiàn)這個(gè)方法,但是大部分我們用都是系統(tǒng)的Widget,比如StatefulWidget和StatelessWidget,他們都默認(rèn)實(shí)現(xiàn)了這方法,這方法也非常簡(jiǎn)單,創(chuàng)建了一個(gè)Element。這里面隱含了一很重要問(wèn)題的答案,開(kāi)篇我們問(wèn)了這樣的問(wèn)題,這個(gè)三個(gè)對(duì)象是什么關(guān)系?現(xiàn)在我們至少知道了Widget和Element的關(guān)系了,一個(gè)Widget有一個(gè)Element對(duì)象,是通過(guò)createElement()創(chuàng)建的。
- 注釋2
canUpdate()方法很簡(jiǎn)單,就是判斷oldWidget和newWidget是不是同一個(gè)Widget,如果他們的runtimeType和key相同,就認(rèn)為是同一個(gè)Widget。
Widget的特性
Widget是一個(gè)很重要的概念,但是Widget有一個(gè)更重重要的特性,就是Widget是immutable(不可變的)的,這是什么意思?下面我們講解一下,我們拿Opacity為例給大家講解,講解之前我們先看一下Opacity的繼承關(guān)系。(在講源碼之前我們先看一下Opacity的職責(zé)是什么,Opacity是一個(gè)能讓他的孩子透明的組件,很簡(jiǎn)單也很容易理解。)
class Opacity extends SingleChildRenderObjectWidget {
}
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
}
abstract class RenderObjectWidget extends Widget {
}
從上面可以看出來(lái),Opacity繼承自SingleChildRenderObjectWidget,這類(lèi)只包含了一個(gè)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處聲明了一個(gè)屬性,這屬性是final,也就除了構(gòu)造函數(shù)能給這個(gè)屬性賦值之外,沒(méi)有其他的辦法讓這個(gè)值進(jìn)行改變。那我們想改變這個(gè)值怎么辦,唯一的辦法就是創(chuàng)建一個(gè)新的Opacity。
為什么這樣設(shè)計(jì)呢?先透露一下這是Flutter的核心設(shè)計(jì)哲學(xué),在接下來(lái)的章節(jié)中我們將詳細(xì)為大家講解。
總結(jié)
Widget好像是Android得一個(gè)xml配置文件,不參與真正的渲染,只是告訴渲染層我長(zhǎng)什么樣式,并且這個(gè)對(duì)象的屬性是不可以改變的,要想改變只能重現(xiàn)創(chuàng)建一個(gè)對(duì)象。
Element概述
Element定義
還是老規(guī)矩,先看一下定義。
An instantiation of a [Widget] at a particular location in the tree.
翻譯過(guò)來(lái)的大概意思就是,"在Element表示一個(gè)Widget樹(shù)中特定位置的實(shí)例",下面我們看一下Element類(lèi)的源代碼。
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個(gè)重點(diǎn)的方法和1個(gè)重要的屬性,其實(shí)Element的屬性和方法非常多,通過(guò)構(gòu)造函數(shù)可以看出來(lái),一個(gè)Element持有一個(gè)Widget,下面我們分析一下Element的創(chuàng)建過(guò)程。
Element創(chuàng)建
通過(guò)上面的Widget概述那一節(jié)我們知道,Widget有一個(gè)抽象方法createElement(),用來(lái)創(chuàng)建Element的,這個(gè)方法的具體實(shí)現(xiàn)有很多,我們找一個(gè)上面我們分析過(guò)的SingleChildRenderObjectWidget,這個(gè)類(lèi)非常簡(jiǎn)單,只有一個(gè)child,下面看一這個(gè)類(lèi)的源碼。
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
const SingleChildRenderObjectWidget({ Key key, this.child }) : super(key: key);
final Widget child;
@override
SingleChildRenderObjectElement createElement()//注釋1
=> SingleChildRenderObjectElement(this);
}
上面已經(jīng)說(shuō)過(guò)這個(gè)類(lèi)的繼承關(guān)系,這個(gè)類(lèi)繼承RenderObjectWidget,構(gòu)造函數(shù)也很簡(jiǎn)單,傳入一個(gè)child,重要的是在注釋1處,這個(gè)類(lèi)創(chuàng)建一個(gè)類(lèi),是SingleChildRenderObjectElement,通過(guò)名字猜想,這一定是一個(gè)Element了,下面我們就分析一下SingleChildRenderObjectElement類(lèi)。
這里驗(yàn)證了,Weight和Element的關(guān)系,一個(gè)Widget有一個(gè)Element對(duì)象,是通過(guò)createElement()創(chuàng)建的。
還在分析之前,我們先看一下SingleChildRenderObjectElement的繼承關(guān)系,下面是SingleChildRenderObjectElement的繼承關(guān)系。
class SingleChildRenderObjectElement extends RenderObjectElement {
}
abstract class RenderObjectElement extends Element {
}
從上面的繼承關(guān)系可以看出來(lái),SingleChildRenderObjectElement繼承RenderObjectElement,而RenderObjectElement是一個(gè)Element,大家是不是發(fā)現(xiàn)這繼承關(guān)系和SingleChildRenderObjectWidget非常像,下面是SingleChildRenderObjectElement的源碼。
mount方法調(diào)用
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
}
}
構(gòu)造函數(shù)比較簡(jiǎn)單,下面我們看一下看,mount方法(到這里有的人一定會(huì)問(wèn)了,為什么上來(lái)就分析mount方法呢?等下一篇文章,我們分析一下Flutter的啟動(dòng)過(guò)程就清楚了),這個(gè)方法是當(dāng)新創(chuàng)建的元素第一次添加到樹(shù)中時(shí),框架會(huì)調(diào)用此函數(shù)。
-
注釋1
這方法非常關(guān)鍵,我們將用一個(gè)小節(jié)專(zhuān)門(mén)去分析,請(qǐng)查看
updateChild分析小節(jié)。 -
注釋2
注釋2處,調(diào)用了父類(lèi)的mount,我們看一下父類(lèi)的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處,我們發(fā)現(xiàn)RenderObjectElement還持有一個(gè)對(duì)象,這對(duì)象是RenderObject,我們好像明白了一點(diǎn)什么,Element分別持有Widget和RenderObject,到這里我們解答了這個(gè)三個(gè)對(duì)象是什么關(guān)系?的問(wèn)題,一個(gè)Element包含一個(gè)RenderObject和一個(gè)Widget。
- 注釋2
重點(diǎn)在注釋2的地方,這里創(chuàng)建了一個(gè)RenderObject,調(diào)用的是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樹(shù)上的祖先Element,如果祖先不為空,就調(diào)用insertChildRenderObject方法,這個(gè)方法的意思就是把renderObject的child替換成newSlot。
- 注釋2
用于更新布局?jǐn)?shù)據(jù)的一些信息,這些信息對(duì)于后面的布局至關(guān)重要。
總結(jié)
當(dāng)SingleChildRenderObjectElement被SingleChildRenderObjectWidget創(chuàng)建成功之后,系統(tǒng)會(huì)調(diào)用SingleChildRenderObjectElement的mount(),這個(gè)方法首先調(diào)用super.mount(),也就是上圖的第一步。
RenderObjectElement的mount()先創(chuàng)建了一個(gè)RenderObject對(duì)象,也就是第二步,創(chuàng)建這個(gè)對(duì)象是在Widget類(lèi)中創(chuàng)建的。
第三步,就是把這個(gè)將RenderObject添加到指定的位置的渲染樹(shù)中。