Flutter是一個優秀的UI框架,它能夠幫助我們快速的構建出漂亮的使用者介面。只需要很少的程式碼和熱過載,你的APP就能夠擁有高達120fps的流暢性。但是,你是否想過Flutter是如何做到這一切的呢?Flutter使用了什麼樣的魔法來實現這一切的呢?或者說Flutter內部是如何工作的呢?我們將會探索這一切,請拿杯茶或者咖啡然後繼續閱讀下去吧。也許你已經聽過Flutter中一切皆為Widget。你的APP是個Widget、Text是個Widget,Widget周圍的padding也是Widget,甚至recognise gestures(手勢識別)也是一個Widget。但是這些並不是全部的事實。如果我告訴你Widget的確很棒,能夠幫助你快速的構建出APP,但是我不使用任何一個Widget就能夠完成App的構建你相信嗎?讓我們先來深入框架來看看如何做到這一切吧。####The Four Layers也許你已經在一些類似於‘Flutter入門介紹’的文章中對Flutter有了比較大致的瞭解。但是你並沒有能夠真正的理解這些層級所代表的概念。也許你像我一樣看著這張圖看了20s卻不知道怎麼理解。不用擔心,我會幫助你的。看下下面的這個圖吧。
Flutter framework由許多抽象的層級組成。在這些層級的最頂端是我們經常用到的Material和Cupertino Widgets。我們大多數情況下使用的就是這兩類Widget。在Widget層下面,你會發現Rendering層。Rendering層簡化了佈局和繪製過程。它是dart:ui的的抽象化。dart:ui是框架的最底層,它負責處理與Engine層的交流溝通。簡而言之,等級越高的層越容易使用,但是等級越低的層,暴露出來的api越多,越能夠增加自定義功能。####1. The dart:ui library
所以你可以僅僅通過使用例項化dart:ui庫中的類(例如Canvas、Paint和TextField)來構建一個Flutter App。但是如果你對於直接在canvas上繪製比較熟悉,就會知道使用這些底層api繪製一個圖案是既難又繁瑣的。接下來考慮一些不是繪製的東西吧,例如佈局和命中測試。這些意味著什麼呢?這意味著你必須手動的計算所有在你佈局中使用的座標。然後混合一些繪製和命中測試來捕獲使用者的輸入。對每一幀進行上述操作並追蹤它們。這個方法對於那些比較簡單的APP,比如一個在藍色區域內展示文字這種比較適用。如果對於那些比較複雜的APP或者簡單的遊戲來說可夠你受的了。更不用說產品經理最喜愛的動畫、滾動和一些酷炫的UI效果了。用我多年的開發經驗告訴你,這些是開發者無窮無盡的夢魘。####2. The Rendering library
大多數情況下我們會使用一些“基礎”Widget來組成我們需要的Widget。例如我們使用GestureDetec來包裹Container,Container中包裹Button來處理按鈕點選。這叫做組合而不是繼承。然而除了自己構建每個UI元件,Flutter團隊還建立了兩個包含常用的Material和Cupertino風格的Widgets的庫。4. The Material & Cupertino library
那什麼又是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不同的原因。####Computer the Next Frame因為Widget是不可變的,當某個Widget的配置改變的時候,整個Widget樹都需要被重建。例如當我們改變一個Container的顏色為紅色的時候,框架就會觸發一個重建整個Widget樹的動作。然後在Element的幫助下,Flutter比較新的Widget樹中的第一個Widget型別和RenderObject樹中第一個RenderObject的型別。接下來比較Widget樹中第二個Widget和RenderObject樹中第二個RenderObject的型別,以此類推,直到Widget樹和RendObject樹比較完成。
然後完成了,新的RenderObject樹已經被重建,並將會計算佈局,然後繪製在螢幕上面。Flutter內部使用了很多優化方法和快取策略來處理,所以你不需要手動處理這個。####Conclusion現在你應該對Flutter為什麼能以如此快的速度渲染複雜佈局有了大致的瞭解了。我希望這篇文章能夠幫助你更好的理解Flutter內部的設計理念。我的Twitter是 Frederik Schweiger,期待與你的交流。