作為系列文章的第六篇,本篇主要在前文的探索下瓦糟,針對(duì)描述一下 Widget 中的一些有意思的原理筒愚。
文章匯總地址:
首先我們需要明白,Widget 是什么菩浙?這里有一個(gè) “總所周知” 的答就是:Widget并不真正的渲染對(duì)象 巢掺。是的扯再,事實(shí)上在 Flutter 中渲染是經(jīng)歷了從 Widget
到 Element
再到 RenderObject
的過程。
我們都知道 Widget 是不可變的址遇,那么 Widget 是如何在不可變中去構(gòu)建畫面的熄阻?上面我們知道,Widget
是需要轉(zhuǎn)化為 Element
去渲染的倔约,而從下圖注釋可以看到秃殉,事實(shí)上 Widget 只是 Element 的一個(gè)配置描述 ,告訴 Element 這個(gè)實(shí)例如何去渲染浸剩。
那么 Widget 和 Element 之間是怎樣的對(duì)應(yīng)關(guān)系呢钾军?從上圖注釋也可知: Widget 和 Element 之間是一對(duì)多的關(guān)系 。實(shí)際上渲染樹是由 Element 實(shí)例的節(jié)點(diǎn)構(gòu)成的樹绢要,而作為配置文件的 Widget 可能被復(fù)用到樹的多個(gè)部分吏恭,對(duì)應(yīng)產(chǎn)生多個(gè) Element 對(duì)象。
那么RenderObject
又是什么重罪?它和上述兩個(gè)的關(guān)系是什么樱哼?從源碼注釋寫著 An object in the render tree
可以看出到 RenderObject
才是實(shí)際的渲染對(duì)象,而通過 Element 源碼我們可以看出:Element 持有 RenderObject 和 Widget剿配。
再結(jié)合下圖搅幅,可以大致總結(jié)出三者的關(guān)系是:配置文件 Widget 生成了 Element,而后創(chuàng)建 RenderObject 關(guān)聯(lián)到 Element 的內(nèi)部 renderObject
對(duì)象上呼胚,最后Flutter 通過 RenderObject 數(shù)據(jù)來布局和繪制茄唐。 理論上你也可以認(rèn)為 RenderObject 是最終給 Flutter 的渲染數(shù)據(jù),它保存了大小和位置等信息蝇更,F(xiàn)lutter 通過它去繪制出畫面沪编。
說到 RenderObject
,就不得不說 RenderBox
:A render object in a 2D Cartesian coordinate system
年扩,從源碼注釋可以看出蚁廓,它是在繼承 RenderObject
基礎(chǔ)的布局和繪制功能上,實(shí)現(xiàn)了“笛卡爾坐標(biāo)系”:以 Top常遂、Left 為基點(diǎn)纳令,通過寬高兩個(gè)軸實(shí)現(xiàn)布局和嵌套的。
RenderBox 避免了直接使用 RenderObject
的麻煩場(chǎng)景克胳,其中 RenderBox
的布局和計(jì)算大小是在 performLayout()
和 performResize()
這兩個(gè)方法中去處理平绩,很多時(shí)候我們更多的是選擇繼承 RenderBox
去實(shí)現(xiàn)自定義。
綜合上述情況漠另,我們知道:
- Widget只是顯示的數(shù)據(jù)配置捏雌,所以相對(duì)而言是輕量級(jí)的存在,而 Flutter 中對(duì) Widget 的也做了一定的優(yōu)化笆搓,所以每次改變狀態(tài)導(dǎo)致的 Widget 重構(gòu)并不會(huì)有太大的問題性湿。
- RenderObject 就不同了纬傲,RenderObject 涉及到布局、計(jì)算肤频、繪制等流程叹括,要是每次都全部重新創(chuàng)建開銷就比較大了。
所以針對(duì)是否每次都需要?jiǎng)?chuàng)建出新的 Element 和 RenderObject 對(duì)象宵荒,Widget 都做了對(duì)應(yīng)的判斷以便于復(fù)用汁雷,比如:在 newWidget
與oldWidget
的 runtimeType 和 key 相等時(shí)會(huì)選擇使用 newWidget
去更新已經(jīng)存在的 Element 對(duì)象,不然就選擇重新創(chuàng)建新的 Element报咳。
由此可知:Widget 重新創(chuàng)建侠讯,Element 樹和 RenderObject 樹并不會(huì)完全重新創(chuàng)建。
看到這暑刃,說個(gè)題外話:那一般我們可以怎么獲取布局的大小和位置呢厢漩?
首先這里需要用到我們前文中提過的 GlobalKey
,通過 key 去獲取到控件對(duì)象的 BuildContext
岩臣,而我們也知道 BuildContext
的實(shí)現(xiàn)其實(shí)是 Element
溜嗜,而Element
持有 RenderObject
。So婿脸,我們知道的 RenderObject
粱胜,實(shí)際上獲取到的就是 RenderBox
,那么通過 RenderBox
我們就只大小和位置了狐树。
showSizes() {
RenderBox renderBoxRed = fileListKey.currentContext.findRenderObject();
print(renderBoxRed.size);
}
showPositions() {
RenderBox renderBoxRed = fileListKey.currentContext.findRenderObject();
print(renderBoxRed.localToGlobal(Offset.zero));
}
--
自此,第六篇終于結(jié)束了鸿脓!(///▽///)
資源推薦
- Github : https://github.com/CarGuo/
- 開源 Flutter 完整項(xiàng)目:https://github.com/CarGuo/GSYGithubAppFlutter
- 開源 Flutter 多案例學(xué)習(xí)型項(xiàng)目: https://github.com/CarGuo/GSYFlutterDemo
- 開源 Fluttre 實(shí)戰(zhàn)電子書項(xiàng)目:https://github.com/CarGuo/GSYFlutterBook