# 三顆渲染樹是如何運作的? ## 職責(What) - Widget Tree:UI 藍圖、設定檔 - Element Tree:UI 生命週期的管理 - RenderObject Tree:UI 佈局與繪製 ## 相互關係(Why & How) ### 性能優化 Declarative(陳述式) vs Imperative(命令式) - Imperative: ```jave= TextView.setText // 改變文字,不會重建TextView ``` - Declarative: 禁止直接改變 `RenderObject` 的屬性。 > 遵循陳述式的 `UI = f(state)`,在每次更新的時候,重新建立一份**完整包含所有參數、不會被改變的 UI 設定檔**,並透過它來更新`RenderObject`。而這個所謂的 UI 設定檔,就是我們最熟悉的`Widget`。 ### 重建成本 Widget 的重建成本小,RenderObject的重建成本大 > 產生一個新的 widget instance 是很廉價,因為產生它只會生成資料不會直接生成 `Element` 和 `RenderObject`,昂貴與否得看你怎麼去變動你的 layout widget 那些。 ### 誰產生誰,什麼時候產生(Who & When) 以 `RichText` 為例: ```dart= class RichText extends MultiChildRenderObjectWidget { ... @override RenderParagraph createRenderObject(BuildContext context) { ... } @override void updateRenderObject(BuildContext context, RenderParagraph renderObject) { ... } } ``` 但 `Widget` 在什麼時候,被誰呼叫,去建立 `RenderObject`? ```dart= abstract class RenderObjectElement extends Element { @override void mount(Element parent, dynamic newSlot) { ... _renderObject = widget.createRenderObject(this); ... _dirty = false; } } ``` - 什麼時候 -> `mount`。 - 被誰呼叫 -> `Element`。 ## Element Widget: XML/xib RenderObject: View/UIView **Element: Life cycle** ![element](https://ithelp.ithome.com.tw/upload/images/20200905/20129053MRhfoJHzfH.png) ### 但 `Element` 在什麼時候,被誰產生? `Widget` 的 `createElement`,並在下面兩個地方呼叫: 1. `RenderObjectToWidgetAdapter.attachToRenderTree` 2. `Element.inflateWidget` #### 1. RenderObjectToWidgetAdapter `RenderObjectToWidgetAdapter` 是整個 Flutter 中的第一個 `Widget`,也是由它來建立 Element Tree 中的第一個 `Element`。之後的 Child Element 都是由 Parent Element 來建立的。 ```dart= class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget { RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) { if (element == null) { ... element = createElement(); // HERE ... } else { element._newWidget = this; element.markNeedsBuild(); } return element; } ``` #### 2. Element.inflateWidget `Widget` 會建立其對應的 `Element`,並將自身傳入作為參數 ```dart= abstract class Element extends DiagnosticableTree implements BuildContext { @protected Element inflateWidget(Widget newWidget, dynamic newSlot) { .... final Element newChild = newWidget.createElement(); // AND HERE .... } } ``` > `inflateWidget` 最主要的工作就是透過 `Widget` 建立 `Element` ## 各自的繼承關係圖,以及它們之間的對應關係 ![trees](https://ithelp.ithome.com.tw/upload/images/20200906/20129053r4L3PiWuZw.png) 1. `Widget` 和 `Element` 之間有著明顯的一對一的關係。 2. 只有 `RenderObjectElement` 才會呼叫 `RenderObjectWidget` 去產生 `RenderObject`。 > 簡單來說,既然整個 Widget Tree 是一個由各種 Widget 複合出來的樹,我們要去渲染它時自然會遞迴地去呼叫 StatelessWidget 和 StatefulWidget 的 build 函數,直到我們遇到某個沒有 build 函數的 RenderObjectWidget,就由它產生其對應的 RenderObject。 ### Example ```dart= void main() { runApp(Container( alignment: Alignment.center, child: Text( 'Hello, World!', textDirection: TextDirection.ltr, ), )); } ``` ![example](https://ithelp.ithome.com.tw/upload/images/20200906/20129053oOIcCkKOu3.png) 1. 複合的 `Widget`,例如 `Container`。 2. 只有在遇到 `RenderObjectWidget` `時才會去產生RenderObject`。 # 範例:Run App -> 建立渲染樹 -> 更新渲染樹 ## Run App 這裡的 `RenderObjectToWidgetAdapter` 是整個 Flutter 中的第一個 `Widget`,也是由它來建立 Element Tree 中的第一個 `Element`。之後的Child Element 都是由 Parent Element 來建立的。 ### Step 1: ![step 1](https://ithelp.ithome.com.tw/upload/images/20200907/20129053ixY6RzovVC.png) ### Step 2: ![step 2](https://ithelp.ithome.com.tw/upload/images/20200907/20129053ZgzMoxdTTs.png) ### Step 3: ![step 3](https://ithelp.ithome.com.tw/upload/images/20200907/20129053qEMnLgYC8u.png) ### Step 4: ![step 4](https://ithelp.ithome.com.tw/upload/images/20200907/20129053DjPGzoKJZj.png) ### Step 5: ![step 5](https://ithelp.ithome.com.tw/upload/images/20200907/20129053V4r3Gtb7vp.png) ### Step 6: ![step 6](https://ithelp.ithome.com.tw/upload/images/20200907/20129053TeR6gUYTH6.png) ### Step 7: ![step 7](https://ithelp.ithome.com.tw/upload/images/20200907/20129053lJsBjY7ns5.png) ### Step 8: ![step 8](https://ithelp.ithome.com.tw/upload/images/20200907/201290536aIpOBht9F.png) ### Step 9: ![step 9](https://ithelp.ithome.com.tw/upload/images/20200908/20129053y914v73XoD.png) ### Step 10: ![step 10](https://ithelp.ithome.com.tw/upload/images/20200908/20129053jcl4JWmoZb.png) ### Step 11: ![step 11](https://ithelp.ithome.com.tw/upload/images/20200908/201290535jitwUADt1.png) ## 更新 Widget Tree `setState` -> `build` -> `updateChild` ```dart= // In Element @protected @pragma('vm:prefer-inline') Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) { .... } ``` | | newWidget == null | newWidget != null | |---------------|---------------|------| | child == null | Returns null. | Returns new Element. | | child != null | Old child is removed, returns null. | Old child updated if possible, returns child or new Element. | ```dart= // In Widget static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; } ``` # Ref - https://ithelp.ithome.com.tw/articles/10234299 - https://juejin.cn/post/7103491448985059359 - https://medium.com/jastzeonic/flutter-render-%E7%9A%84%E9%82%A3%E4%B8%80%E5%85%A9%E4%BB%B6%E4%BA%8B%E6%83%85-d372ed9a9962 ###### tags: `fugle study group`