Flutter中ListView複用原理探索

Flutter三棵樹之間的關係

衆所周知flutter中有三顆非常重要的樹形結構,分別爲widget樹,element樹和renderObject樹
其中widget樹,存放渲染內容,只是一個數據結構。創建和銷燬十分輕量,在頁面的刷新過程中經常會被重建。就個人而言,可以把widget抽象的理解爲一個存放配置信息的map。
element樹,同時持有widget和renderObject,存放上下文信息,可以說是widget和renderObject的連接件。
renderObject樹,負責layout和paint事件。
顯然,相對於widget來說,element和renderObject的創建和銷燬會使用相當多的資源,那麼在這個整個flutter中,複用的應該是element和renderObject,而複用widget其實是個誤區。

widget的創建流程

想要明確複用原理,就要先知道創建原理。舉一個最簡單的例子,對於app下只有一個text,那麼這個text的創建流程是這樣的。
根widget調用其build方法
之後會執行updateChild方法中
這裏的newWidget就是text,不爲空,child由於從未創建過所以爲空,直接執行inflateWidget方法。
這個方法中實際上就是調用了該widget的createElement方法,創建了對應的element對象,之後通過mount方法將該element對象掛在到element 樹的對應位置上。
之後假如該widget還有child的話,就會調用該widget的performRebuild方法,重複整個流程直到遍歷完整棵樹。
簡單來說,就是Framework 調用Widget.createElement 創建一個Element實例,記爲element
再調用 element.mount,mount方法中首先調用element所對應Widget的createRenderObject方法創建與element相關聯的RenderObject對象,然後調用element.attachRenderObject方法將element.renderObject添加到渲染樹中插槽指定的位置。
當父Widget發生變化,此時就需要重新構建對應的Element樹。爲了進行Element複用,在Element重新構建前會先嚐試是否可以複用舊樹上相同位置的element,element節點在更新前都會調用其對應Widget的canUpdate方法,如果返回true,則複用舊Element,舊的Element會使用新Widget配置數據更新,反之則會創建一個新的Element。在canUpdate方法中通過對比runtimeType和key來確定是否可以複用。
此外如果變化的widget完全相同,但處於不同的位置,將不進行銷燬重建,而是直接更改其位置。

ListView複用

有了以上的基礎,理解listview的複用應該會簡單一些,舉個例子。
在這裏插入圖片描述這是一顆listview結構對應的樹,假設左邊的item爲A,右邊的爲B,此時滑動listview,A將要滑出屏幕,假設下一個進入屏幕的item與B一樣,爲了進行復用,就會檢查舊樹上相同位置的element,按照圖中的例子來說,column、text、row、row、image、text、text都可以進行復用,而新創建的只有第一個row下的text,這樣對比下來,flutter中listview的複用在佈局相似時資源的佔用確實比較小。

一個小建議

在創建widget時,儘量保證佈局的最大相似性,也即通過判斷數據的有無來判斷子widget是寬高爲0的container還是實際的佈局,將其寫到同一個方法中,也就能保證最大程度的佈局相似。
在這裏插入圖片描述比如這兩個item,就可以在方法中判斷圖片鏈接是否爲空,爲空則圖片位置爲寬高爲0的container,否則爲image,其餘佈局完全相同。
而對於比較極端的情況:在這裏插入圖片描述上面的item明顯是一個column,而下面的既可以是column也可以是row,那麼爲了資源的節省,推薦一併寫成column。