你可曾想過(guò) Flutter 是如何處理 Widgets 并將他們轉(zhuǎn)換成像素顯示在屏幕上的葵孤?還沒(méi)有担钮?
你應(yīng)該思考一下。
是否理解系統(tǒng)的底層實(shí)現(xiàn)原理是區(qū)分一個(gè)優(yōu)秀程序員的關(guān)鍵尤仍。
當(dāng)你了解什么最有效的時(shí)候箫津,你才能更輕松地創(chuàng)建布局和特效,從而節(jié)省大量的時(shí)間宰啦。
這篇文章的目的是向你介紹 Flutter 內(nèi)部的工作原理苏遥,我們將從不同的角度來(lái)看 Flutter,理解它到底是如何工作的赡模。
讓我們開(kāi)始吧
你可能已經(jīng)知道如何使用 StatelessWidget
和 StatefulWidget
了田炭,但是它們只是用來(lái)組裝控件的容器,布局和繪制的工作是在其他地方完成的漓柑。
我強(qiáng)烈建議你打開(kāi)自己喜歡的 IDE 并繼續(xù)閱讀教硫,只有看著實(shí)際的代碼才能讓你感到“噢,原來(lái)是這樣的”欺缘。在 Intellij 中栋豫,你可以通過(guò)雙擊 shift 并輸入類(lèi)名來(lái)查找相應(yīng)代碼。
Opacity
為了熟悉 Flutter 工作的基本原理谚殊,我們先來(lái)看一個(gè)最基礎(chǔ)的控件 Opacity
丧鸯,它將是一個(gè)很好的例子。
Opacity
接收一個(gè) child嫩絮,所以你可以用 Opacity
來(lái)包裝任意的 Widget 從而改變它的外觀丛肢。另外,它還接收一個(gè)名為 opacity
的屬性剿干,用來(lái)設(shè)置控件的不透明度蜂怎,取值在 0.0 到 1.0 之間。
Widget
Opacity
是一個(gè) SingleChildRenderObjectWidget
置尔。
這個(gè)類(lèi)的繼承關(guān)系如下:
Opacity → SingleChildRenderObjectWidget → RenderObjectWidget → Widget
相應(yīng)的杠步,StatelessWidget
和 StatefulWidget
的繼承關(guān)系如下:
StatelessWidget / StatefulWidget→Widget
它們的不同之處在于,Stateless / StatefulWidget 只是將其他 Widget
組裝起來(lái)榜轿,而 Opacity
會(huì)真正地影響 Widget
的繪制幽歼。
但是如果你去那些代碼中找的話,你是不可能找到任何與屬性 opacity 相關(guān)的繪制代碼谬盐。
那是因?yàn)?Widget 僅僅只持有控件的配置信息甸私。比如這個(gè)例子中,控件 Opacity
只是用來(lái)持有屬性 opacity 的飞傀。
這也就是你每次都可以在
build()
函數(shù)中新建widget
的原因皇型。構(gòu)建widget
的過(guò)程并不耗費(fèi)資源诬烹,因?yàn)?Wiget 只是用來(lái)保存屬性的容器。
Rendering
那么渲染是在哪完成的呢弃鸦?
答案是 RenderingObject
绞吁。
正如你能從名字中猜出的那樣,RenderingObject
負(fù)責(zé)渲染相關(guān)的工作寡键。
Opacity
通過(guò)下面這些方法來(lái)創(chuàng)建和更新 RenderingObject:
@override
RenderOpacity createRenderObject(BuildContext context) => new RenderOpacity(opacity: opacity);
@override
void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
renderObject.opacity = opacity;
}
RenderOpacity
除了繪制掀泳,Opacity
和它的 child 幾乎一模一樣,它用 child 的大小作為自身大小西轩。在繪制它的 child 之前员舵,它給 child 增加了一個(gè)不透明度。
所以藕畔,RenderOpacity
需要實(shí)現(xiàn)包括布局马僻、點(diǎn)擊檢測(cè)、計(jì)算大小在內(nèi)的所有的函數(shù)注服,并將其轉(zhuǎn)交給它的 child 來(lái)完成(也就相當(dāng)于一個(gè) child 的代理)韭邓。
RenderOpacity
繼承于 RenderProxyBox
,RenderProxyBox
中實(shí)現(xiàn)了向 child 的工作交接溶弟。
double get opacity => _opacity;
double _opacity;
set opacity(double value) {
_opacity = value;
markNeedsPaint();
}
在 setter 方法中除了設(shè)置字段的值外女淑,還調(diào)用了 markNeedsPaint()
(或者 markNeedsLayout()
),顧名思義辜御,它告訴系統(tǒng)“我已經(jīng)發(fā)生了改變鸭你,請(qǐng)重新進(jìn)行繪制(或布局)”。
在 RenderOpacity
中擒权,我們找到了下面這個(gè)方法:
@override
void paint(PaintingContext context, Offset offset) {
context.pushOpacity(offset, _alpha, super.paint);
}
PaintingContext
就是進(jìn)行繪制操作的畫(huà)布袱巨,這里通過(guò)在 canvas 上調(diào)用名為pushOpacity
的方法來(lái)實(shí)現(xiàn)不透明度的控制。
回顧一下
-
Opacity
不是StatelessWidget
或者StatefulWidget
碳抄,而是SingleChildRenderObjectWidget
愉老; - Widget 僅用于存儲(chǔ)渲染所需要的信息;
- 在這里剖效,
Opacity
存儲(chǔ)了一個(gè)雙精度值的不透明度嫉入; - 布局和渲染等操作實(shí)際是由繼承至
RenderProxyBox
的RenderOpacity
完成的; - 因?yàn)?
Opacity
并不改變 child 的其他行為璧尸,所以它的每個(gè)方法都僅僅只是 child 的代理劝贸; - 通過(guò)重載
paint
方法并調(diào)用pushOpacity
,RenderOpacity 實(shí)現(xiàn)了向 Widget 添加不透明度的需求逗宁。
That’s it? Kind of.
記住,Widget 只是一個(gè)配置梦湘,RenderObject
負(fù)責(zé)管理布局瞎颗、繪制等操作件甥。
在 Flutter 中,你基本上一直都在不停的創(chuàng)建 Widgets哼拔,當(dāng) build()
方法被調(diào)用時(shí)引有,你創(chuàng)建了一堆 Widgets。
每當(dāng)有什么變化產(chǎn)生的時(shí)候倦逐,build()
方法都會(huì)被調(diào)用譬正。例如播放一個(gè)動(dòng)畫(huà),build()
方法就會(huì)被頻繁調(diào)用檬姥。這意味著你不能總是重新構(gòu)建一整顆渲染樹(shù)曾我,相反,你應(yīng)該做的知識(shí)去更新這顆樹(shù)健民。
你無(wú)法獲取一個(gè) widget 在屏幕上的位置和大小抒巢,因?yàn)?widget 就像一張藍(lán)圖,它并非真實(shí)地顯示在屏幕之上秉犹,它只描述了底層渲染對(duì)象應(yīng)該具有的那些屬性蛉谜。
Element
Element
是這顆巨大的控件樹(shù)上的實(shí)體。
基本上會(huì)發(fā)生什么:
在第一次創(chuàng)建 Widget
的時(shí)候崇堵,會(huì)對(duì)應(yīng)創(chuàng)建一個(gè) Element
型诚, 然后將該元素插入樹(shù)中。如果之后 Widget
發(fā)生了變化鸳劳,則將其與舊的 Widget
進(jìn)行比較狰贯,并且相應(yīng)地更新 Element
。重要的是棍辕,Element
被不會(huì)重建暮现,只是更新而已。
Elements 是 Flutter 核心框架的重要組成部分楚昭,顯然它并不僅僅如此栖袋,但目前對(duì)我們來(lái)說(shuō),知道這些就足夠了抚太。
在 Opacity 示例中塘幅,element 是在哪創(chuàng)建的?
SingleChildRenderObjectWidget
中創(chuàng)建了它:
@override
SingleChildRenderObjectElement createElement() => new SingleChildRenderObjectElement(this);
而 SingleChildRenderObjectElement
則是一個(gè)僅擁有一個(gè) child 的元素尿贫。
Element 創(chuàng)建 RenderObject电媳,但在示例中是 Widget 創(chuàng)建的 RenderObject?
這僅僅是為了平滑的 API庆亡,因?yàn)槌R?jiàn)的情況是 Widget
需要一個(gè) RenderObject
而不是自定義 Element
匾乓。RenderObject
實(shí)際是由 Element
來(lái)創(chuàng)建的,讓我們來(lái)看看又谋。
SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);
在構(gòu)造函數(shù)中拼缝,SingleChildRenderObjectElement
拿到了一個(gè) RenderObjectWidget
的引用(其中包含了創(chuàng)建 RenderObject
的方法)娱局。
Element
通過(guò) mount
方法插入到 Element Tree 中,這里就是 Element
創(chuàng)建 RenderObject
的地方:
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
一旦 Element
被掛載到樹(shù)上咧七,它便會(huì)向 Widget
請(qǐng)求 “請(qǐng)給我你要使用的 RenderObject衰齐,這樣我就能保存它了”。
結(jié)語(yǔ)
這就是 Opacity
控件內(nèi)部的工作方式继阻。
這篇文章的目標(biāo)是向你介紹 widget 之外的世界耻涛。這里任然還有很多話題要討論,但我希望你已經(jīng)很好地理解了其內(nèi)部的工作原理瘟檩。