日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

element中有多个合计_深入理解 Flutter 中的 Widget, Element, RenderObject

發(fā)布時間:2025/3/19 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 element中有多个合计_深入理解 Flutter 中的 Widget, Element, RenderObject 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

這篇文章基于 Flutter stable v1.7 總結(jié)下 Flutter 當前的 UI 系統(tǒng)以及相關(guān)的概念, 在最后會通過自己組合一個 Gradient Button 按鈕的方式來熟悉 Flutter 的一些 UI 實現(xiàn)。

Flutter 框架整體分層:

下面會主要關(guān)注 Widgets 和 Rendering, 也會涉及到一點 Painting 部分的內(nèi)容。

Flutter 三層樹

React 中,Component 對象并不是真正負責渲染,需要框架生成 element 后,進行初始渲染或者 diff 后 patch 變更。Flutter 也借鑒了 React 的設(shè)計,Widget 并不是真正的渲染對象,真正的渲染是經(jīng)歷了從?Widget?=>?Element=>?RenderObject?的過程,與 React 拼接成 dom 交給瀏覽器渲染不同,Flutter 中 RenderObject 負責來在繪制引擎的上層進行繪制。

先來看下類分布及其職責:

-- Widget 存儲配置信息,另外其由于是 immutable 的,所以會不斷重新創(chuàng)建刷新。-- Element 是分離 Widget 和真正渲染對象的中間層,很多控制渲染的行為在這層去處理。-- RenderObject 來真正的執(zhí)行 Diff, Hit Test, 布局以及繪制。

三層對象構(gòu)成的樹之間的關(guān)聯(lián)則如下圖表示:

如圖可知,Widget 會創(chuàng)建 Element,然后 Element 創(chuàng)建相應(yīng)的 RenderObject,Element 就是 Widget 在 UI 樹具體位置的一個對象,一個 Widget 可能有多個 Element,大多的 Element 只有唯一的 renderObject,但也有一些 Element 會有多個子節(jié)點,比如 MultiChildRenderObjectElement。最終所有 RenderObject 構(gòu)成 render tree 。

Stateful & Stateless Widget

關(guān)于 StatefulWidget 以及 StatelessWidget 的使用,已經(jīng)有很多的文章進行了描述,這些 Widget 可能不是傳統(tǒng)原生開發(fā)中的 UI 控件,也可能是布局,語義化或者主題等等,通過這些組件組合堆疊,來完成應(yīng)用界面的繪制。

在一開始接觸 Flutter 時會有點抵觸這個 State 職責定位,因為涉及到構(gòu)建樹的 build 方法也需要在 State 中重載。不過整體看下來,這么設(shè)計也是基于 Flutter 的一部分機制導致的,首先 Widget 本身也分為 UI 和功能型,另外 Widget 樹會隨著 State 的變化而變化,每次 build 都會重新生成樹,Widget 會頻繁的銷毀重建而 State 對象則不會,所以這么看來由 State 對象來返回樹會更合適一些。

State 的生命周期

  • initState 方法會在 State 初始化時調(diào)用,由于 State 對象會被 Framework 長期持有,所以該方法在其生命周期中只調(diào)用一次,我們經(jīng)常會在這做一些一次性的操作,比如狀態(tài)初始化,訂閱事件等。

  • didChangeDependencies 這個方法會在初始化后調(diào)用一次,直到 State 對象的依賴發(fā)生變化時才會被調(diào)用,比如上層的樹中包含 InheritedWidget,如果其發(fā)生了變化,那么此時 InhertiedWidget 的子 widget 的 didChangeDependencies() 都會被調(diào)用。典型的場景是一些全局的配置比如 Locale, Theme, 或者 Redux 的 StoreProvider 組件改變會導致子樹收到該消息并 re-build 組件樹。另外如果有些行為不希望在每次 build 都觸發(fā),也可以考慮到將其放到 didChangeDenpendencies 中來。

  • didUpdateWidget 當 widget 的配置變化時,會調(diào)用該方法并觸發(fā) build

  • build 返回改組件構(gòu)建的樹結(jié)構(gòu)

  • deactivate 當前組件暫時被從視圖樹中移除時會調(diào)用該方法,比如頁面切換或者應(yīng)用掛起都會觸發(fā)這個方法。

  • dispose 永久移除時作為析構(gòu)函數(shù),可以在這里做一些資源的釋放操作。

上面有提到 InheritedWidget,這個類的存在主要來解決需要逐層傳遞 State 的問題,當我們有 State 需要共享時,就可以將其放在一個繼承 InheritedWidget 的類中,然后使用的組件直接取用就可以。常見與其相關(guān)的場景比如 Theme.of(context), Locale.of(context), ButtonTheme.of(context) 等等。

Element

Element 的生命周期

  • Widget.createElement?創(chuàng)建一個 Element 實例。

  • element.mount()?會讓其 widget?createRenderObject?并將對象 attach 到渲染樹中插槽指定的位置,插入后該 element 標記為 'active' 狀態(tài)。

  • 當 widget 的配置數(shù)據(jù)改變時,為了對 element 進行復用,Framework 在決定重新創(chuàng)建 Element 前會先嘗試復用相同位置舊的 element, 調(diào)用對應(yīng)的 widget 的 canUpdate() 方法來確定是否更新,canUpdate()方法主要判斷新舊 widget 的 runtimeType 以及 key, 所以我們可以通過指定不同的 Key 來強制刷新。

  • 當有祖先元素決定要移除 element 時,會調(diào)用?deactivateChild?方法來移除孩子,移除后?element.renderObject?也會被從渲染樹中移除,然后 Framework 會調(diào)用?element.deactivate?方法,這時 element 標記為 'inactive' 狀態(tài)。

  • 'inactive' 態(tài)的 element 將不會再顯示到屏幕。為了避免在一次動畫執(zhí)行過程中反復創(chuàng)建、移除某個特定 element, 'inactive' 態(tài)的 element 在當前動畫最后一幀結(jié)束前都會保留,如果動畫結(jié)束后不能重新 'active', 則會調(diào)用?unmount?方法將其徹底移除,這是 element 狀態(tài)標為 defunct 。

  • 如果 element 要重新插入到 Element 樹其它位置,如 element 或 element 的祖先擁有一個 GlobalKey, 那么 Framework 會先將 element 從現(xiàn)有位置移除,然后再調(diào)用?activate?方法,并將其 renderObject 重新 attach 到渲染樹。

  • BuildContext

    我們在 build widgets 時都會重載 build 方法,build 方法有一個參數(shù)就是 BuildContext 上下文對象,我們可以拿到 context 去其祖先尋找指定類型的對象,比如 Theme, Navigator, Localizations 等等。

    文檔表示的很清楚,BuildContext 對象實際上就是 Element 對象, 它設(shè)計成抽象接口類主要為了屏蔽使用者對 Element 進行操作。而我們常用的?of?操作則是調(diào)用了 Element 的?inheritFromWidgetOfExactType()?方法。

    以?Theme.of(context)?為例,我們可以看到它的實現(xiàn):

    這里會調(diào)用 context 也就是 element 的 inheritFromWidgetOfExactType 方法拿到指定類型的對象,返回一個經(jīng)過本地化的主題數(shù)據(jù)。

    inheritFromWidgetOfExactType 方法也很簡單,直接從緩存的哈希表里找到擁有指定對象類型的祖先節(jié)點,然后返回給調(diào)用方,緩存中沒有則添加到緩存中。另外值得注意的是,這里的哈希表就是之前在 Widget state 里提到的 dependencies 。

    BuildOwner

    上文看到 BuildContext 中有一個 getter 方法來獲取 buildOwner, 那么 BuildOwner 具體會做些什么呢?

    它作為 widgets 的管理者,會追蹤哪些 widget 需要重建,并且處理一些組件樹上的其他任務(wù)比如管理 inactive 的 element 列表,或者觸發(fā) reassemble 命令當 hot reload 的時候。最主要的 build owner 是由 WidgetsBinding 持有的,它會被操作系統(tǒng)驅(qū)動去執(zhí)行 build/layout/paint 的 pipeline。

    另外 build owners 也被用來管理 off-screen 的組件樹,谷歌官方介紹組件樹的視頻有提到,每當 widget 改變需要重新渲染時,framework 會在繪制的 idle time 去計算要新渲染的樹,完成后直接對當前的進行替換并渲染。利用 idle time 的方式也很類似 React 的 Fiber 設(shè)計 (利用瀏覽器 requestIdleCallback api)。

    布局及繪制

    Flutter 界面渲染過程分為三個階段:布局,繪制和合成,布局和繪制會在 Flutter 框架中完成,而合成則交給引擎負責:

    前文有提到每個 Element 都會對應(yīng)一個 RenderObject,它的職責主要則是布局和繪制,所有的 RenderObject 會組成一棵渲染樹 RenderTree 。

    RenderObject 擁有一個 parent 和一個 parentData 插槽,parentData 這個預(yù)留變量正是由 parent 來賦值,parent 通常會通過子 RenderObject 來存儲一些和子元素相關(guān)的數(shù)據(jù)比如偏移量。當然,其不僅僅可以存儲偏移信息,通常所有和子節(jié)點特定的數(shù)據(jù)都可以存儲到子節(jié)點的 parentData 中,如?ContainerBox?中該屬性就保存了指向兄弟節(jié)點的?previousSibling?和?nextSibling,Element 的?visitChildren()?方法也是通過它們來實現(xiàn)對子節(jié)點的同層級(廣度)遍歷。

    RenderObject 類本身實現(xiàn)了一套基礎(chǔ)的 layout 和繪制協(xié)議,但是并沒有定義子節(jié)點模型,坐標系統(tǒng)以及具體的布局協(xié)議。為此,Flutter 提供了一個 RenderBox 類,繼承自 RenderObject,坐標系采用笛卡爾坐標系。

    布局過程

    渲染樹種每個節(jié)點都會接受父節(jié)點的 Contraints 參數(shù),決定自己大小,然后父節(jié)點就可以按照自己的邏輯決定各個子節(jié)點的位置,完成布局過程。

    具體來看,RenderBox 中有一個 size 屬性用來保存寬高,RenderBox 的 layout 是通過在組件樹上從上往下傳遞 BoxConstraints 對象實現(xiàn)的,它可以限制子節(jié)點的最大和最小寬高,布局階段,父節(jié)點會調(diào)用子節(jié)點的?layout()?方法,大致實現(xiàn)如下:

    布局前先要確定?relayoutBoundary,該參數(shù)標識當前節(jié)點是否是布局邊界。即當邊界內(nèi)的節(jié)點發(fā)生重新布局時,不會影響邊界外的節(jié)點。

    在 Element 層,如果其被標記為 dirty 時(通過?markNeedsBuild())則會重新 build,這時 RenderObject 便會重新布局。在 RenderObject 中則有一個?markNeedsLayout()?方法,它會將 RenderObject 的布局狀態(tài)標記為 dirty,這樣在下一幀便會重新 layout,我們來看下該方法:

    確定?relayoutBoundary?是不是自己,不是則繼續(xù)向上尋找,是則告知 buildOwner 當前節(jié)點需要布局,并調(diào)用了更新方法。?

    另外在?layout()?方法中我們看到其是通過?performLayout()?來去真正的執(zhí)行布局,則總結(jié)起來調(diào)用順序為:?layout() > performResize()/perforLayout() > child.layout() > ...?如此遞歸完成整個 UI 樹的布局。如果需要子類化 RenderBox 類來定制布局,則應(yīng)該通過重寫?performResize?和?performLayout?來實現(xiàn),而不是?layout?。

    另外有一個問題,除非顯式的指定 size,很多控件在 build 時是不清楚具體尺寸的,但很多時候我們需要提前清楚 size 來做一些操作或者布局,閑魚團隊的?深入了解Flutter界面開發(fā)?中有提到可以在 layout 階段后發(fā)送一個 notification 通知上層,另外也可以并推薦的方式是通過膠水層 WidgetsBinding 注冊一個 PostFrameCallback (WidgetsBinding.instance.addPostFrameCallback),然后回調(diào)里通過

    _listViewKey.currentContext.findRenderObject().paintBounds.size.width;

    的方式來拿到尺寸。

    繪制過程

    RenderObject 可以通過 paint() 方法來完成具體繪制邏輯,流程和布局類似。這里以 RenderFlex 的 paint 方法為例說明:

    defaultPaint:

    由于 Flex 屬于一個布局類,自身沒有需要繪制的部分,則直接遍歷子節(jié)點并調(diào)用 paintChild 方法觸發(fā)子節(jié)點繪制。

    渲染流程中也有個與 relayoutBoundary 對應(yīng)的屬性 repaintBoundary,用于確定重繪邊界,提高繪制效率,避免繪制的干擾以及不必要的重繪。

    RenderObject 中有一個?isRepaintBoundary?屬性,決定重繪時是否獨立于其父元素,若為 true 則單獨建立圖層繪制。可以看下?paintChild()?方法:

    如果子節(jié)點是 repaintBoundary 則會調(diào)用?_compositeChild?方法并將偏移量傳遞過去,不是則直接從上下文進行繪制。

    另外看下觸發(fā)重繪的?markNeedsPaint()?方法:

    與 layout 類似,會判斷邊界并交給 pipeline owner 去做相關(guān)工作。

    在 iOS 中,不同職責的 layer 組合在一起組成了 view,Flutter 中剛才可以看到如果有一個 repaintBoundary 區(qū)域形成時,框架會創(chuàng)建一個 Layer,不同 Lyaer 也是可以獨立工作的,比如 OffsetLayer 在 RenderObject 中就是用來做定位繪制的。

    其次在 RenderObject 中有一個屬性為 needsCompositing,它會影響生成多少層的 Layer, 而這些 Layer 又會組成一棵 Layer Tree ... 也就是實際去給引擎繪制的樹。

    實現(xiàn)一個 Button

    最后,我們會以框架實現(xiàn) Button 的方式來實現(xiàn)一個帶漸變背景的按鈕:

    語義化在最頂層,使用?Semantics?來包裝整個 Widget;管理焦點(1.7 新增)使用?Focus;布局和焦點用到兩個 'Box',分別為?ConstrainedBox?和?DecoratedBox;樣式及點擊水波效果通過?Material?和?Inkwell;Default 的樣式則通過全局的?Theme,?ButtonTheme?和?IconTheme?來控制;

    綜上,build 方法大概如下:

    -- EOF --以上為本篇文章的全部內(nèi)容,歡迎提出建議和指正,

    參考資源:

    • Github/flutter

    • flutter

    • RenderObject和RenderBox

    • 深入了解Flutter界面開發(fā)

    • Flutter的原理及美團的實踐

    • 深入繪制原理

    • 基于JS的高性能Flutter動態(tài)化框架MXFlutter

    • flutter-playlist-youtube

    總結(jié)

    以上是生活随笔為你收集整理的element中有多个合计_深入理解 Flutter 中的 Widget, Element, RenderObject的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。