Flutter完整開發實戰詳解(六、 深入Widget原理)

作為系列文章的第六篇,本篇主要在前文的探索下,針對描述一下 Widget 中的一些有意思的原理。

文章匯總地址:

Flutter 完整實戰實戰系列文章專欄

Flutter 番外的世界系列文章專欄

首先我們需要明白,Widget 是什么?這里有一個 “總所周知” 的答就是:Widget并不真正的渲染對象 。是的,事實上在 Flutter 中渲染是經歷了從 WidgetElement 再到 RenderObject 的過程。

我們都知道 Widget 是不可變的,那么 Widget 是如何在不可變中去構建畫面的?上面我們知道,Widget 是需要轉化為 Element 去渲染的,而從下圖注釋可以看到,事實上 Widget 只是 Element 的一個配置描述 ,告訴 Element 這個實例如何去渲染。

image

那么 Widget 和 Element 之間是怎樣的對應關系呢?從上圖注釋也可知: Widget 和 Element 之間是一對多的關系 。實際上渲染樹是由 Element 實例的節點構成的樹,而作為配置文件的 Widget 可能被復用到樹的多個部分,對應產生多個 Element 對象。

那么RenderObject 又是什么?它和上述兩個的關系是什么?從源碼注釋寫著 An object in the render tree 可以看出到 RenderObject 才是實際的渲染對象,而通過 Element 源碼我們可以看出:Element 持有 RenderObject 和 Widget。

image

再結合下圖,可以大致總結出三者的關系是:配置文件 Widget 生成了 Element,而后創建 RenderObject 關聯到 Element 的內部 renderObject 對象上,最后Flutter 通過 RenderObject 數據來布局和繪制。 理論上你也可以認為 RenderObject 是最終給 Flutter 的渲染數據,它保存了大小和位置等信息,Flutter 通過它去繪制出畫面。

image

說到 RenderObject ,就不得不說 RenderBoxA render object in a 2D Cartesian coordinate system,從源碼注釋可以看出,它是在繼承 RenderObject 基礎的布局和繪制功能上,實現了“笛卡爾坐標系”:以 Top、Left 為基點,通過寬高兩個軸實現布局和嵌套的。

RenderBox 避免了直接使用 RenderObject 的麻煩場景,其中 RenderBox 的布局和計算大小是在 performLayout()performResize() 這兩個方法中去處理,很多時候我們更多的是選擇繼承 RenderBox 去實現自定義。

綜合上述情況,我們知道:

  • Widget只是顯示的數據配置,所以相對而言是輕量級的存在,而 Flutter 中對 Widget 的也做了一定的優化,所以每次改變狀態導致的 Widget 重構并不會有太大的問題。
  • RenderObject 就不同了,RenderObject 涉及到布局、計算、繪制等流程,要是每次都全部重新創建開銷就比較大了。

所以針對是否每次都需要創建出新的 Element 和 RenderObject 對象,Widget 都做了對應的判斷以便于復用,比如:在 newWidgetoldWidgetruntimeTypekey 相等時會選擇使用 newWidget 去更新已經存在的 Element 對象,不然就選擇重新創建新的 Element。

由此可知:Widget 重新創建,Element 樹和 RenderObject 樹并不會完全重新創建。

看到這,說個題外話:那一般我們可以怎么獲取布局的大小和位置呢?

首先這里需要用到我們前文中提過的 GlobalKey ,通過 key 去獲取到控件對象的 BuildContext,而我們也知道 BuildContext 的實現其實是 Element,而Element持有 RenderObject 。So,我們知道的 RenderObject ,實際上獲取到的就是 RenderBox ,那么通過 RenderBox 我們就只大小和位置了。

  showSizes() {
    RenderBox renderBoxRed = fileListKey.currentContext.findRenderObject();
    print(renderBoxRed.size);
  }

  showPositions() {
    RenderBox renderBoxRed = fileListKey.currentContext.findRenderObject();
    print(renderBoxRed.localToGlobal(Offset.zero));
  }

--

自此,第六篇終于結束了!(///▽///)

資源推薦

完整開源項目推薦:
我們還會再見嗎?
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容