本文已經獲得做者的容許,將其原文The Layer Cake翻譯成中文。鑑於本人的英語能力以及表達能力有限,請英語水平足夠的朋友前往原文地址去閱讀=。=。 算法
Flutter是一個優秀的UI框架,它可以幫助咱們快速的構建出漂亮的用戶界面。只須要不多的代碼和熱重載,你的APP就可以擁有高達120fps的流暢性。可是,你是否想過Flutter是如何作到這一切的呢?Flutter使用了什麼樣的魔法來實現這一切的呢?或者說Flutter內部是如何工做的呢?咱們將會探索這一切,請拿杯茶或者咖啡而後繼續閱讀下去吧。 也許你已經聽過Flutter中一切皆爲Widget
。你的APP是個
Widget
、Text是個
Widget
,
Widget
周圍的
padding
也是
Widget
,甚至
recognise gestures
(手勢識別)也是一個
Widget
。可是這些並非所有的事實。若是我告訴你
Widget
的確很棒,可以幫助你快速的構建出APP,可是我不使用任何一個
Widget
就可以完成App的構建你相信嗎?讓咱們先來深刻框架來看看如何作到這一切吧。
也許你已經在一些相似於‘Flutter入門介紹’的文章中對Flutter有了比較大體的瞭解。可是你並無可以真正的理解這些層級所表明的概念。也許你像我同樣看着這張圖看了20s殊不知道怎麼理解。不用擔憂,我會幫助你的。看下下面的這個圖吧。 canvas
Flutter framework由許多抽象的層級組成。在這些層級的最頂端是咱們常常用到的Material
和
Cupertino
Widgets。咱們大多數狀況下使用的就是這兩類Widget。 在Widget層下面,你會發現
Rendering
層。
Rendering
層簡化了佈局和繪製過程。它是
dart:ui
的的抽象化。
dart:ui
是框架的最底層,它負責處理與
Engine
層的交流溝通。 簡而言之,等級越高的層越容易使用,可是等級越低的層,暴露出來的api越多,越可以增長自定義功能。
dart:ui library
暴露出最底層的服務,這些服務被用來引導Application,例如用來驅動輸入、繪製文字、佈局和渲染子系統。api
因此你能夠僅僅經過使用實例化dart:ui
庫中的類(例如Canvas
、Paint
和TextField
)來構建一個Flutter App。可是若是你對於直接在canvas
上繪製比較熟悉,就會知道使用這些底層api繪製一個圖案是既難又繁瑣的。 接下來考慮一些不是繪製的東西吧,例如佈局和命中測試。 這些意味着什麼呢? 這意味着你必須手動的計算全部在你佈局中使用的座標。而後混合一些繪製和命中測試來捕獲用戶的輸入。對每一幀進行上述操做並追蹤它們。這個方法對於那些比較簡單的APP,好比一個在藍色區域內展現文字這種比較適用。若是對於那些比較複雜的APP或者簡單的遊戲來講可夠你受的了。更不用說產品經理最喜好的動畫、滾動和一些酷炫的UI效果了。用我多年的開發經驗告訴你,這些是開發者無窮無盡的夢魘。緩存
Flutter的
Rendering tree
(渲染樹)。RenderObject
的層級結構被Flutter Widgets庫使用來實現其佈局和後臺的繪製。一般來講,儘管你可能會使用RenderBox
來在你的應用中實現自定義的效果,可是大多數狀況下咱們惟一與RenderObject
的交互就是在調試佈局信息的時候。框架
Rendering library
是dart:ui library
上第一個抽象層。它替你作了全部繁重的數學計算工做(例如跟蹤須要不斷計算的座標)。它使用RenderObjects
來處理這些工做。你能夠把RenderObjects
想象成一個汽車的發動機,它承擔了全部把你的APP展現到屏幕的工做。Rendering tree
中的全部RenderObjects
都會被Flutter分層和繪製。爲了優化這個複雜的過程,Flutter使用了一個智能算法來緩存這些實例化很耗費性能的對象從而實如今性能最優化。 大多數狀況,你會發現Flutter使用RenderBox
而不是RenderObject
。這是由於項目的構建者發現使用一個簡單和盒佈局約束就可以成功的構建出有效穩定的UI。想象一下全部的Widget都被放置在它們的盒中。這個盒中的相關參數都計算好了,而後被放置到其餘已經整理好的盒中間。因此若是在你的佈局中僅有一個Widget改變了,只須要裝載其的盒被系統從新計算便可。less
Flutter Widgets框架工具
Widget庫或許是最有意思的庫。它是另一個用來提供開箱即用的Widget的抽象層。這個庫中全部的Widget都屬於如下三種使用適當的RenderObject
處理的Widget之一。佈局
Layout
例如Column
和Row
Widgets用來幫助咱們輕鬆的處理其餘Widget的佈局。Painting
例如Text
和Image
Widgets容許咱們展現(繪製)一些內容在屏幕上。Hit-Testing
例如GestureDetector
容許咱們識別出不一樣的手勢,例如點擊和滑動。大多數狀況下咱們會使用一些「基礎」Widget來組成咱們須要的Widget。例如咱們使用GestureDetec
來包裹Container
,Container
中包裹Button來處理按鈕點擊。這叫作組合而不是繼承。 然而除了本身構建每一個UI組件,Flutter團隊還建立了兩個包含經常使用的Material
和Cupertino
風格的Widgets的庫。性能
使用Material和Cupertino設計規範的Widgets庫。學習
Flutter爲了減小開發者的負擔,建立了這個擁有Material
和Cupertino
風格的Widgets層。
RenderObject
是如何與Widgets
鏈接起來的呢?Flutter是如何建立佈局?Element又是什麼呢? 已經說的夠多了,讓咱們在實踐中學習吧。考慮以下Widgets樹。
Stateless Widget
組成:
SimpleApp
、
SimpleContainer
、
SimpleText
。因此若是咱們調用Flutter的runApp()方法會發生什麼呢? 當
runApp()
被調用時,第一時間會在後臺發生如下事件。
createElement()
來建立相應的Element對象,最後將這些對象組建成Element
樹。Element
經過createRenderObject()
建立的RenderObject。 下圖是Flutter通過這三個步驟後的狀態:
Flutter建立了三個不一樣的樹,一個對應着Widget
,一個對應着Element
,一個對應着RenderObject
。每個Element
中都有着相對應的Widget
和RenderObject
的引用。
那什麼又是RenderObject
呢? RenderObject
中包含了全部用來渲染實例Widget
的邏輯。它負責layout
、painting
和hit-testing
。它的生成十分耗費性能,因此咱們應該儘量的緩存它。咱們把它在內存中儘量的保存更長的時間,甚至回收利用它們(由於它們的實例化真的很耗費資源)。這個時候Element
就登場了。Element
是存在於可變Widget
樹和不可變RenderObject
樹之間的橋樑。Element
擅長比較兩個Object
,在Flutter裏面就是Widget
和RenderObject
。它的做用是配置好Widget
在樹中的位置,而且保持對於相對應的RenderObject
和Widget
的引用。 爲何使用三個樹而不是一個樹呢? 簡而言之是爲了性能。當Widget
樹改變的時候,Flutter使用Element
樹來比較新的Widget
樹和原來的RenderObject
樹。若是某一個位置的Widget
和RenderObject
類型不一致,才須要從新建立RenderObject
。若是其餘位置的Widget
和RenderObject
類型一致,則只須要修改RenderObject
的配置,不用進行耗費性能的RenderObject
的實例化工做了。由於Widget
是很是輕量級的,實例化耗費的性能不多,因此它是描述APP的狀態(也就是configuration)的最好工具。重量級的RenderObject
(建立十分耗費性能)則須要儘量少的建立,並儘量的複用。就像Simon所說:整個Flutter APP就像是一個RecycleView。 然而,在框架中,Element
是被抽離開來的,因此你不須要常常和它們打交道。每一個Widget的build(BuildContext context)
方法中傳遞的context
就是實現了BuildContext
接口的Element
,這也就是爲何相同類別的單個Widget
不一樣的緣由。
由於Widget
是不可變的,當某個Widget
的配置改變的時候,整個Widget
樹都須要被重建。例如當咱們改變一個Container
的顏色爲紅色的時候,框架就會觸發一個重建整個Widget
樹的動做。而後在Element
的幫助下,Flutter比較新的Widget
樹中的第一個Widget
類型和RenderObject
樹中第一個RenderObject
的類型。接下來比較Widget
樹中第二個Widget
和RenderObject
樹中第二個RenderObject
的類型,以此類推,直到Widget
樹和RendObject
樹比較完成。
Widget
和老的Widget
是不是同一個類型。 若是不是同一個類型,那就把
Widget
、
Element
、
RenderObject
分別從它們的樹(包括它們的子樹)上移除,而後建立新的對象。 若是是一個類型,那就僅僅修改
RenderObject
中的配置,而後繼續向下遍歷。 在咱們的例子中,
SimpleApp Widget
是和原來同樣的類型,它的配置也是和原來的
SimpleAppRender
同樣的,因此什麼都不會發生。下一個item在Widget樹中是
SimpleContainer Widget
,它的類型和原來是同樣的,可是它的顏色變化了,
RenderObject
的配置發生變化了。由於
SimpleObject
仍然須要一個
SimpleContainerRender
來渲染,Flutter只是更新了
SimpleContainerRender
的顏色屬性,而後要求它從新渲染。其餘的對象都保持不變。
這個過程是很是快的,由於Flutter很是擅長建立那些輕量級的
Widgets
。那些重量級的
RenderObject
則是保持不變,直到與其相對應類型的
Widget
從
Widget
樹中被移除。那若是Widget的類型發生改變了會發生什麼呢?
和原來同樣,Flutter會對Widget樹的頂端向下遍歷,與
RenderObject
樹中的
RenderObject
類型進行對比。
由於
SimpleButton
的類型與
Element
樹中相對應位置的
Element
的類型不一樣(實際上仍是與
RenderObject
的類型進行比較),Flutter將會從各自的樹上刪除這個
Element
和相對應的
SimpleTextRender
。而後Flutter將會重建與
SimpleButton
相對應的
Element
和
RenderObject
。
而後新的
RenderObject
樹已經被重建,並將會計算佈局,而後繪製在屏幕上面。Flutter內部使用了不少優化方法和緩存策略來處理,因此你不須要手動處理這個。
如今你應該對Flutter爲何能以如此快的速度渲染複雜佈局有了大體的瞭解了。我但願這篇文章可以幫助你更好的理解Flutter內部的設計理念。個人Twitter是 Frederik Schweiger,期待與你的交流。