深入了解 Flutter

這份文件說明 Flutter 工具組的內部運作,讓 Flutter 的 API 成為可能。由於 Flutter 小工具是使用積極的組合來建構,使用 Flutter 建構的使用者介面有大量的元件。為了支援這個工作負載,Flutter 使用次線性演算法來配置和建構元件,以及讓樹狀結構手術有效率且有許多常數因子最佳化的資料結構。透過一些額外的詳細資料,這個設計也讓開發人員可以輕鬆使用回呼來建立無限捲動清單,建構使用者可見的那些元件。

積極的組合

Flutter 最特別的其中一個面向是其積極的組合。元件是透過組合其他元件來建構,而這些元件本身是由更基本的元件建構而成。例如,Padding 是元件,而不是其他元件的屬性。因此,使用 Flutter 建構的使用者介面包含非常多個元件。

元件建構遞迴在 RenderObjectWidgets 中結束,這些元件會在底層渲染樹中建立節點。渲染樹是儲存使用者介面幾何的資料結構,會在配置期間計算,並在繪製點選測試期間使用。大部分的 Flutter 開發人員不會直接撰寫渲染物件,而是使用元件來操作渲染樹。

為了在元件層支援積極的組合,Flutter 在元件和渲染樹層使用許多有效率的演算法和最佳化,說明如下小節。

次線性配置

在有大量小工具和渲染物件的情況下,效能表現良好的關鍵在於有效率的演算法。最重要的就是版面配置效能,這是一種決定渲染物件幾何形狀(例如大小和位置)的演算法。其他一些工具組使用 O(N²) 或更差的版面配置演算法(例如,某些約束領域中的定點迭代)。Flutter 的目標是讓初始版面配置的效能呈線性,而在常見的後續更新既有版面配置的情況下,則版面配置效能次於線性。一般來說,花費在版面配置的時間應當比渲染物件的數量成長得慢。

Flutter 每個畫面執行一次版面配置,而且版面配置演算法在單一行程中運作。父物件呼叫每個子物件的版面配置方法,將約束傳遞到樹狀結構中。子物件遞迴執行自己的版面配置,然後從其版面配置方法傳回幾何形狀到樹狀結構中。重要的是,一旦渲染物件從其版面配置方法傳回,該渲染物件就不會再被拜訪1,直到下一個畫面的版面配置。這種做法將原本可能是分開的測量和版面配置行程合併成單一行程,因此在版面配置期間,每個渲染物件最多只會被拜訪兩次2:一次在傳遞到樹狀結構的過程中,一次在傳遞到樹狀結構的過程中。

Flutter 有這個一般協定的幾個專門化版本。最常見的專門化版本是 RenderBox,它在二維笛卡兒座標中運作。在方塊版面配置中,約束是一個最小和最大寬度,以及一個最小和最大高度。在版面配置期間,子物件會透過在這些界線內選擇一個大小來決定其幾何形狀。在子物件從版面配置傳回後,父物件會決定子物件在其座標系統中的位置3。請注意,子物件的版面配置不能依賴於其位置,因為位置是在子物件從版面配置傳回後才決定的。因此,父物件可以自由重新定位子物件,而不需要重新計算其版面配置。

更一般而言,在佈局期間,唯一從父層傳遞至子層的資訊是限制條件,而唯一從子層傳遞至父層的資訊是幾何形狀。這些不變式可以減少佈局期間所需的工作量

  • 如果子層未將其自己的佈局標記為髒污,則子層可以立即從佈局中返回,中斷漫遊,只要父層給予子層與子層在先前佈局期間收到的限制條件相同即可。

  • 每當父層呼叫子層的佈局方法時,父層會指出它是否使用子層傳回的大小資訊。如果父層不使用大小資訊(這時常發生),則如果子層選擇新的尺寸,父層不需要重新計算其佈局,因為父層有保證,新的尺寸將符合現有的限制條件。

  • 嚴格限制條件是可以由一個有效幾何形狀滿足的限制條件。例如,如果最小和最大寬度相等,且最小和最大高度相等,則滿足這些限制條件的唯一尺寸是具有該寬度和高度的尺寸。如果父層提供嚴格限制條件,則即使父層在其佈局中使用子層的尺寸,父層也不需要在子層重新計算其佈局時重新計算其佈局,因為子層無法在沒有父層的新限制條件下變更尺寸。

  • 渲染物件可以宣告它僅使用父層提供的限制條件來決定其幾何形狀。此宣告會告知架構,即使限制條件不嚴格,即使父層的佈局依賴於子層的尺寸,該渲染物件的父層也不需要在子層重新計算其佈局時重新計算其佈局,因為子層無法在沒有父層的新限制條件下變更尺寸。

由於這些最佳化,當渲染物件樹包含髒污節點時,只有這些節點及其周圍的子樹的有限部分會在佈局期間受到拜訪。

次線性小工具建置

與配置演算法類似,Flutter 的小工具建構演算法是次線性的。建構後,小工具會由元素樹持有,該元素樹保留使用者介面的邏輯結構。元素樹是必要的,因為小工具本身是不可變的,這表示(除其他事項外)它們無法記住與其他小工具的父項或子項關係。元素樹也持有與有狀態小工具相關聯的狀態物件。

回應使用者輸入(或其他刺激)時,元素可能會變為髒的,例如如果開發人員對關聯的狀態物件呼叫 setState()。架構會保留髒元素清單,並在建構階段直接跳到這些元素,略過乾淨的元素。在建構階段,資訊會單向向下傳遞至元素樹,這表示每個元素在建構階段最多會被拜訪一次。一旦清理後,元素不會再變髒,因為根據歸納法,其所有祖先元素也都是乾淨的4

由於小工具是不可變的,如果元素未將自己標記為髒的,則如果父項使用相同的小工具重新建構元素,元素可以立即從建構中傳回,中斷執行。此外,元素只需比較兩個小工具參考的物件身分,即可確定新小工具與舊小工具相同。開發人員利用此最佳化來實作重新投影模式,其中小工具包含預先建構的子項小工具,儲存在其建構中的成員變數中。

在建構期間,Flutter 也會使用 InheritedWidgets 來避免執行父項鏈。如果小工具通常會執行其父項鏈,例如為了判斷目前的佈景主題顏色,建構階段會變成樹狀結構深度的 O(N²),這可能會因為積極的組合而變得相當龐大。為了避免這些父項執行,架構會透過在每個元素中維護 InheritedWidget 的雜湊表,將資訊向下傳遞至元素樹。通常,許多元素會參考相同的雜湊表,而雜湊表只會在引入新的 InheritedWidget 的元素中變更。

線性調解

與普遍認知相反,Flutter 並未採用樹狀差異演算法。相反地,此架構會獨立使用 O(N) 演算法檢查每個元素的子項清單,以決定是否重複使用元素。子項清單調解演算法會針對下列情況進行最佳化

  • 舊子項清單為空。
  • 兩個清單相同。
  • 清單中只有一個地方插入或移除一個或多個小工具。
  • 如果每個清單都包含具有相同金鑰5的小工具,則兩個小工具會配對。

一般方法是透過比較每個小工具的執行時期類型和金鑰,來比對兩個子項清單的開頭和結尾,並有可能在每個清單中間找到一個包含所有未配對子項的非空範圍。然後,此架構會根據金鑰,將舊子項清單中範圍內的小工具放入雜湊表中。接著,此架構會在新的子項清單中遍歷範圍,並根據金鑰查詢雜湊表以進行配對。未配對的小工具會被捨棄並從頭開始重建,而配對的小工具則會使用其新小工具重建。

樹狀手術

重複使用元素對於效能非常重要,因為元素擁有兩項重要的資料:有狀態小工具的狀態,以及底層的渲染物件。當架構能夠重複使用元素時,使用者介面中該邏輯部分的狀態會被保留,並且先前計算的配置資訊可以重複使用,通常可以避免整個子樹的遍歷。事實上,重複使用元素非常有價值,以致於 Flutter 支援保留狀態和配置資訊的非區域性樹狀變異。

開發人員可以透過將 GlobalKey 與其中一個小工具關聯,來執行非本機樹狀突變。每個全域金鑰在整個應用程式中都是唯一的,並會註冊到特定執行緒的雜湊表中。在建置階段,開發人員可以將具有全域金鑰的小工具移動到元素樹中的任意位置。架構會檢查雜湊表,並將現有元素從其先前位置重新設定父元素到其新位置,保留整個子樹,而不是在該位置建置新的元素。

重新設定父元素的子樹中的渲染物件能夠保留其配置資訊,因為配置限制是唯一從父元素傳遞到子元素的資訊。新的父元素會標示為配置的髒污,因為其子元素清單已變更,但如果新的父元素傳遞給子元素與子元素從其舊父元素接收到的相同配置限制,則子元素可以立即從配置傳回,切斷執行。

開發人員廣泛使用全域金鑰和非本機樹狀突變來達成英雄過場動畫和導覽等效果。

常數因子最佳化

除了這些演算法最佳化之外,達成積極的可組合性也仰賴幾個重要的常數因子最佳化。這些最佳化在上面討論的主要演算法的葉子中是最重要的。

  • 不依賴子模型。與大多數工具組不同,Flutter 的渲染樹不會承諾特定子模型,這些工具組會使用子清單。例如,RenderBox 類別有一個抽象的 visitChildren() 方法,而不是具體的 firstChildnextSibling 介面。許多子類別只支援單一子項,直接作為成員變數持有,而不是子項清單。例如,RenderPadding 只支援單一子項,因此有一個更簡單的配置方法,執行時間較短。

  • 視覺渲染樹,邏輯小工具樹。在 Flutter 中,渲染樹在與裝置無關的視覺座標系統中運作,這表示 x 座標中的較小值總是朝向左邊,即使目前的閱讀方向是從右到左。小工具樹通常在邏輯座標中運作,意即具有視覺詮釋依賴於閱讀方向的開始結束值。從邏輯座標轉換到視覺座標是在小工具樹和渲染樹之間的移交中完成。這種方法更有效率,因為渲染樹中的配置和繪製計算比小工具到渲染樹的移交更常發生,而且可以避免重複的座標轉換。

  • 由專門的渲染物件處理的文字。絕大多數的渲染物件都不了解文字的複雜性。相反地,文字是由一個專門的渲染物件 RenderParagraph 處理,它是渲染樹中的葉節點。開發人員並非透過子類化一個文字感知的渲染物件,而是使用組合將文字納入其使用者介面。此模式表示只要其父節點提供相同的版面約束(這很常見,即使在樹狀結構手術期間),RenderParagraph 就可避免重新計算其文字版面。

  • 可觀察的物件。Flutter 同時使用模型觀察和反應式範例。顯然地,反應式範例佔主導地位,但 Flutter 對某些葉狀資料結構使用可觀察的模型物件。例如,Animation 會在值變更時通知觀察者清單。Flutter 會將這些可觀察的物件從小工具樹傳遞到渲染樹,渲染樹會直接觀察它們,並僅在它們變更時使管線的適當階段失效。例如,Animation<Color> 的變更可能只會觸發繪製階段,而不是建置和繪製階段。

綜合起來,並在積極組合所建立的大型樹狀結構中進行加總,這些最佳化對效能有顯著的影響。

元素和 RenderObject 樹狀結構的分離

Flutter 中的 RenderObjectElement(小工具)樹狀結構是同構的(嚴格來說,RenderObject 樹狀結構是 Element 樹狀結構的子集)。一個顯而易見的簡化方式是將這些樹狀結構合併成一個樹狀結構。然而,實際上讓這些樹狀結構保持分離有許多好處

  • 效能。當版面變更時,只需要走訪版面樹中相關的部分。由於組合,元素樹經常有許多額外的節點,這些節點必須略過。

  • 清晰度。更清楚的區分問題,讓小工具協定和渲染物件協定都能針對其特定需求進行專業化,簡化 API 介面,進而降低錯誤風險和測試負擔。

  • 類型安全性。渲染物件樹可以更具備類型安全性,因為它可以在執行階段保證子項具有適當的類型(例如,每個座標系統都有其自己的渲染物件類型)。組合小工具可以不論在版面配置期間使用的座標系統為何(例如,在方塊版面配置和細條版面配置中都可以使用顯示應用程式模型一部分的相同小工具),因此在元素樹中,驗證渲染物件的類型需要樹狀走訪。

無限捲動

無限捲動清單對工具組來說出了名的困難。Flutter 支援具有基於建構函式模式的簡單介面的無限捲動清單,其中 ListView 使用回呼函式在捲動時對使用者可見時依需求建構小工具。支援此功能需要視窗感知版面配置依需求建構小工具

視窗感知版面配置

如同 Flutter 中的大多數事物,可捲動小工具是使用組合建置的。可捲動小工具的外側是 視窗,它是一個「內部較大」的方塊,表示其子項可以延伸到視窗的邊界之外,並可捲動至視圖中。不過,視窗並非具有 RenderBox 子項,而是具有 RenderSliver 子項,稱為區塊,它具有一個視窗感知的配置協定。

區塊配置協定與方塊配置協定的結構相符,因為父項會將約束傳遞給其子項,並接收回傳的幾何。不過,這兩個協定之間的約束和幾何資料不同。在區塊協定中,會提供子項有關視窗的資訊,包括剩餘的可見空間量。他們回傳的幾何資料可啟用各種與捲動連結的效果,包括可摺疊標頭和視差。

不同的區塊以不同的方式填滿視窗中可用的空間。例如,產生子項線性清單的區塊會依序配置每個子項,直到區塊用完子項或空間。類似地,產生子項二維格線的區塊只會填滿其格線中可見的部分。由於區塊知道有多少空間可見,因此即使區塊有潛力產生無限數量的子項,它們也可以產生有限數量的子項。

區塊可以組合以建立客製化的可捲動配置和效果。例如,單一視窗可以有一個可摺疊標頭,後面接著一個線性清單,然後是一個格線。這三個區塊會透過區塊配置協定進行合作,只產生實際上透過視窗可見的子項,而不管這些子項屬於標頭、清單或格線6

依需求建立小工具

如果 Flutter 有嚴格的建構然後配置然後繪製管線,前述內容將不足以實作無限捲動清單,因為關於多少空間可透過視窗看見的資訊僅在配置階段時可用。沒有額外的機制,配置階段就太晚建構出填滿空間所需的小工具。Flutter 解決這個問題的方式是交錯管線的建構和配置階段。在配置階段的任何一點,架構都可以依需求開始建構新的小工具只要那些小工具是目前執行配置的渲染物件的後代

建構和配置交錯只因為在建構和配置演算法中嚴格控制資訊傳播。特別是在建構階段,資訊只能向下傳遞。當渲染物件執行配置時,配置巡覽尚未拜訪該渲染物件底下的子樹,這表示在該子樹中建構產生的寫入無法使迄今已輸入配置計算的任何資訊失效。同樣地,一旦配置從渲染物件傳回,該渲染物件在此配置期間將不再被拜訪,這表示後續配置計算產生的任何寫入都無法使用於建構渲染物件子樹的資訊失效。

此外,線性調整和樹狀手術對於在捲動期間有效更新元素,以及在元素捲入和捲出視窗邊緣時修改渲染樹至關重要。

API 人體工學

速度只有在框架能實際有效使用時才重要。為了引導 Flutter 的 API 設計朝向更高的可用性,Flutter 已在廣泛的 UX 研究中反覆進行開發人員測試。這些研究有時會確認現有的設計決策,有時有助於引導功能的優先順序,有時會改變 API 設計的方向。例如,Flutter 的 API 大量採用文件說明;UX 研究確認了此類文件說明的價值,但也特別強調了範例程式碼和說明性圖表的必要性。

本節討論 Flutter 的 API 設計中為協助可用性而做出的部分決策。

針對開發人員的心態調整專門的 API

Flutter 的 WidgetElementRenderObject 樹狀結構中節點的基本類別未定義子項模型。這讓每個節點都能針對適用於該節點的子項模型進行調整。

大多數 Widget 物件只有一個子項 Widget,因此只公開一個 child 參數。有些小工具支援任意數量的子項,並公開一個接受清單的 children 參數。有些小工具完全沒有子項且不保留任何記憶體,也沒有子項參數。類似地,RenderObjects 公開特定於其子項模型的 API。RenderImage 是葉節點,沒有子項概念。RenderPadding 有一個子項,因此儲存一個指向單一子項的指標。RenderFlex 接受任意數量的子項,並將其管理為連結清單。

在某些罕見的情況下,會使用更複雜的子模型。RenderTable 渲染物件的建構函式會使用子項陣列陣列,該類別公開控制列數和欄數的 getter 和 setter,而且有特定方法可依 x,y 座標取代個別子項、新增列、提供子項的新陣列陣列,以及使用單一陣列和欄數取代整個子項清單。在實作中,物件不會像大多數渲染物件一樣使用連結清單,而是使用可索引陣列。

Chip 小工具和 InputDecoration 物件有與相關控制項上存在的插槽相符的欄位。例如,在一個萬用子項模型會強制語意分層在子項清單之上,定義第一個子項為前置詞值,第二個為後置詞,專用子項模型允許使用專用的命名屬性。

這種彈性允許這些樹中的每個節點以最符合其角色的方式進行操作。很少會想要在表格中插入儲存格,導致所有其他儲存格換行;同樣地,很少會想要依索引而非依據參考從彈性列中移除子項。

RenderParagraph 物件是最極端的案例:它有一個完全不同類型的子項,TextSpan。在 RenderParagraph 邊界,RenderObject 樹轉換為 TextSpan 樹。

將 API 專門化以符合開發人員預期的整體方法,不只適用於子模型。

有些相當瑣碎的小工具存在,特別是讓開發人員在尋找問題解決方案時能夠找到它們。一旦知道如何使用 Expanded 小工具和零大小的 SizedBox 子項,就可以輕鬆地為列或欄新增一個空格,但發現該模式是不必要的,因為搜尋 space 會揭示 Spacer 小工具,它直接使用 ExpandedSizedBox 來達成效果。

類似地,只要完全不將小工具子樹包含在建構中,就可以輕鬆地隱藏小工具子樹。然而,開發人員通常預期會有小工具來執行此操作,因此 Visibility 小工具存在於將此模式包裝在瑣碎的可重複使用小工具中。

明確的引數

UI 架構往往有許多屬性,因此開發人員很少能夠記住每個類別的每個建構函式引數的語義含義。由於 Flutter 使用反應式範例,因此 Flutter 中的建構方法通常會對建構函式進行許多呼叫。透過利用 Dart 對命名引數的支持,Flutter 的 API 能夠保持此類建構方法清晰易懂。

此模式延伸至具有多個引數的任何方法,特別是延伸至任何布林引數,因此在方法呼叫中孤立的 truefalse 文字永遠都是自我文件化的。此外,為了避免 API 中雙重否定通常造成的混淆,布林引數和屬性總是命名為肯定形式(例如,enabled: true 而不是 disabled: false)。

避免陷阱

Flutter 架構中許多地方使用的技術是定義 API,使得錯誤條件不存在。這會從考量中移除整類錯誤。

例如,內插函數允許內插的一端或兩端為 null,而不是將其定義為錯誤案例:內插兩個 null 值永遠為 null,而從 null 值內插或內插至 null 值等於內插至給定類型的零類比。這表示意外傳遞 null 至內插函數的開發人員不會遇到錯誤案例,而是會取得合理的結果。

較為微妙的範例在於 Flex 配置演算法。此配置概念在於,給予彈性配置物件的空間會分配給其子物件,因此彈性配置的大小應為可用空間的全部。在原始設計中,提供無限空間會失敗:這表示彈性配置應為無限大小,這是一個無用的配置設定。相反地,API 已調整,因此當無限空間配置給彈性配置物件時,配置物件會調整其大小以符合子物件的所需大小,減少可能的錯誤案例數量。

此方法也用於避免建立允許建立不一致資料的建構函式。例如,PointerDownEvent 建構函式不允許 PointerEventdown 屬性設定為 false(這會造成自我矛盾);相反地,建構函式沒有 down 欄位的參數,且始終將其設定為 true

一般而言,此方法是為輸入網域中的所有值定義有效的詮釋。最簡單的範例是 Color 建構函式。它並非採用四個整數(分別代表紅色、綠色、藍色和 alpha),其中每個整數都可能超出範圍,而是採用單一整數值,並定義每個位元的意義(例如,最底層的八個位元定義紅色元件),因此任何輸入值都是有效的色彩值。

較為精細的範例是 paintImage() 函式。此函式採用 11 個引數,有些引數的輸入網域相當廣,但它們經過仔細設計,大多數彼此正交,因此無效的組合非常少。

積極回報錯誤案例

並非所有錯誤條件都能設計出來。對於那些仍然存在的錯誤,在偵錯建置中,Flutter 通常會嘗試在非常早期偵測錯誤並立即回報。廣泛使用斷言。詳細檢查建構函數參數的健全性。監控生命週期,當偵測到不一致時,會立即導致引發例外狀況。

在某些情況下,這會被推向極端:例如,在執行單元測試時,無論測試執行什麼其他操作,每個佈局的 RenderBox 子類別都會積極檢查其內在大小調整方法是否符合內在大小調整合約。這有助於偵測 API 中的錯誤,否則這些錯誤可能會無法執行。

當引發例外狀況時,它們會包含所有可用的資訊。Flutter 的一些錯誤訊息會主動探查相關堆疊追蹤,以確定實際錯誤最可能的位置。其他錯誤訊息會遍歷相關樹狀結構,以確定不良資料的來源。最常見的錯誤包括詳細說明,在某些情況下包括避免錯誤的範例程式碼,或連結至進一步的文件。

反應式範例

可變的基於樹狀結構的 API 遭受二分法存取模式:建立樹狀結構的原始狀態通常使用與後續更新截然不同的運算集合。Flutter 的呈現層使用此範例,因為這是維護持續樹狀結構的有效方式,而這對於有效配置和繪製至關重要。然而,這表示與呈現層的直接互動充其量只是尷尬,最糟的情況下則是容易出錯。

Flutter 的小工具層級引進一個使用反應式範例7的合成機制,用來操作底層的渲染樹。這個 API 抽象出樹狀結構的處理,將樹狀結構的建立與變異步驟結合為單一的樹狀結構說明(建立)步驟,在系統狀態每次變更後,使用者介面的新組態由開發人員說明,而架構會計算出反映這個新組態所需要的樹狀結構變異系列。

內插

由於 Flutter 的架構鼓勵開發人員說明與目前應用程式狀態相符的介面組態,因此存在一個機制,可以在這些組態之間隱含地進行動畫。

例如,假設在狀態 S1 中,介面由一個圓形組成,但在狀態 S2 中,它由一個正方形組成。如果沒有動畫機制,狀態變更將會造成突兀的介面變更。隱含動畫允許圓形在多個影格中順利地變成正方形。

每個可以隱含進行動畫的功能都有一個有狀態小工具,用來記錄輸入的目前值,並在輸入值變更時開始動畫序列,在指定時間內從目前值過渡到新值。

這是使用 lerp(線性內插)函數和不變物件來實作的。每個狀態(在本例中為圓形和正方形)都表示為一個不變物件,並使用適當的設定(顏色、筆劃寬度等)進行組態,並知道如何繪製自身。當在動畫期間繪製中間步驟時,開始和結束值會傳遞給適當的 lerp 函數,以及一個表示動畫中點的 t 值,其中 0.0 表示 開始,而 1.0 表示 結束8,而函數會傳回第三個不變物件,表示中間階段。

對於圓形到方形的轉換,lerp 函數會傳回一個物件,代表一個「圓角正方形」,其半徑描述為從 t 值衍生的分數、使用顏色 lerp 函數內插的顏色,以及使用雙精度數 lerp 函數內插的描邊寬度。然後,實作與圓形和正方形相同的介面的物件,在收到要求時就能自行繪製。

此技術允許狀態機器、狀態對應組態、動畫機器、內插機器,以及如何繪製每個畫面的特定邏輯完全彼此分離。

此方法廣泛適用。在 Flutter 中,基本類型(例如 ColorShape)可以內插,但更精細的類型(例如 DecorationTextStyleTheme)也可以內插。這些類型通常由本身可以內插的元件建構,而內插更複雜的物件通常只要遞迴內插描述複雜物件的所有值即可。

某些可內插物件由類別階層定義。例如,形狀由 ShapeBorder 介面表示,且存在各種形狀,包括 BeveledRectangleBorderBoxBorderCircleBorderRoundedRectangleBorderStadiumBorder。單一 lerp 函式無法預期所有可能的類型,因此介面改而定義 lerpFromlerpTo 方法,靜態 lerp 方法會延遲到這些方法。當指示從形狀 A 內插到形狀 B 時,會先詢問 B 是否可以 lerpFrom A,然後,如果不行,則會詢問 A 是否可以 lerpTo B。(如果都不行,則函式會傳回 t 值小於 0.5 的 A,否則傳回 B。)

這允許類別階層任意延伸,後續新增的內容能夠在先前已知的數值和它們自己之間內插。

在某些情況下,內插本身無法由任何可用的類別描述,且會定義一個私有類別來描述中間階段。例如,在 CircleBorderRoundedRectangleBorder 之間內插時就會發生這種情況。

此機制還有一個額外的優點:它可以處理從中間階段到新數值的內插。例如,在圓形到正方形轉換過程中的一半,形狀可能會再次變更,導致動畫需要內插到三角形。只要三角形類別可以 lerpFrom 圓角正方形中間類別,就能無縫執行轉換。

結論

Flutter 的口號「一切都像小工具」圍繞著建立使用者介面,透過組成小工具,而小工具又由更基本的組件組成。這種積極的組成方式會產生大量的小工具,需要經過精心設計的演算法和資料結構才能有效率地處理。透過一些額外的設計,這些資料結構也能讓開發人員輕易建立無限捲動清單,在小工具可見時依需求建立小工具。


腳註

1 至少在版面配置上是如此。如有必要,可能會重新檢視繪製、建立無障礙樹狀結構,以及進行點選測試。

2 當然,現實情況會稍微複雜一些。有些版面配置涉及內在維度或基準線測量,這確實會涉及額外走訪相關子樹(積極快取用於減輕最糟情況下二次效能的可能性)。然而,這些情況出奇地罕見。特別是,縮放包裝的常見情況不需要內在維度。

3 技術上來說,子項的位置並非其 RenderBox 幾何的一部分,因此在版面配置期間實際上不需要計算。許多渲染物件會隱含地將其單一子項定位在相對於其自身原點的 0,0,這完全不需要計算或儲存。有些渲染物件會避免在最後一刻(例如,在繪製階段)之前計算其子項的位置,以避免在後續未繪製的情況下進行計算。

4 此規則有一個例外。如 依需求建立小工具 部分所述,某些小工具會因版面限制變更而重新建置。如果小工具在受到版面限制變更影響的同一幀中,因無關原因將自身標記為已修改,則會更新兩次。此重複建置僅限於小工具本身,不會影響其後代。

5 鍵是一個不透明物件,可選擇與小工具關聯,其等號運算子用於影響調整演算法。

6 為了無障礙性,並讓應用程式在小工具建置與顯示在螢幕上之間多出幾毫秒,視窗會在可見小工具前後建立(但不會繪製)數百畫素的小工具。

7 此方法最初是由 Facebook 的 React 函式庫普及。

8 在實務上,t 值允許延伸超過 0.0-1.0 範圍,而且某些曲線確實如此。例如,「彈性」曲線會短暫超過以表示彈跳效果。內插邏輯通常會適當地外推起點或終點。對於某些類型,例如在內插顏色時,t 值會有效地固定在 0.0-1.0 範圍內。