前言
Flutter知識體系中,Widget、Element以及RenderObject是比較重要的三個類族;裡面的知識點十分多,今天側重於給大家分享這三棵樹是如何構建出來的。
類族繼承關係
只列舉一些和本文有關的
Widget:
通過上面的圖可以知道,Widget基類定義了createElement()
函式,子類通過override該方法構造不同的element類;並且還需要重點關注的是,只有RenderObjectWidget類才定義createRenderObject()
函式,所以,能構造出renderObject的都只能是其子類;其次,可以發現只有StatelessWidget類才有build()
函式(StatefulWidget會通過state呼叫build()
),這個在後面會說到。
Element:
從這裡可以看到,element和widget的各個類都基本是對應的
RenderObject:
Widget/Element/RenderObject的初始化過程,以及各自的關係
首先定位到main.dart的入口函式:
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
複製程式碼
WidgetsFlutterBinding主要負責連線Flutter框架層和engine層,這裡通過我們傳入的Widget生成Wiget/Elemtn/RenderObject樹,其他關於WidgetsFlutterBinding這裡就不過多去討論,在這我們只需要知道該類有兩個重要的屬性,分別是Render Tree的根節點,Element Tree的根節點;以及初初始化根節點的方法:
RenderObjectToWidgetAdapter _renderViewElement // 繼承自RenderObjectWidget
RenderView renderView // 繼承自RenderObject
// 初始化rootElement和rootWidget,並把rootRenderObject繫結到rootElement上,把我們寫的Widget(app)繫結到rootWidget上
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
}
複製程式碼
接下來,我們來看一下class RenderObjectToWidgetAdapter的定義:
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
final Widget child;
final RenderObjectWithChildMixin<T> container;
RenderObjectWidget {
RenderObjectToWidgetAdapter({
this.child,
this.container,
this.debugShortDescription,
}) : super(key: GlobalObjectKey(container));
// 把自己作為element建構函式的引數,生成一個element例項
@override
RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
@override
RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element
if (element == null) {
...
// 沒有rootElement就例項化一個
element = createElement();
// 通過mount函式,例項化一個rootRenderObject並掛載到rootElement上
element.mount(null, null);
...
} else {
...
}
// 返回rootElement給外部WidgetsFlutterBinding的_renderViewElement賦值
return element;
}
}
複製程式碼
通過上面的程式碼可以知道,RenderObjectToWidgetAdapter類就是一個介面卡,通過它構造出了整個樹
我們先來看一下根節點是如何初始化生成的,簡要如下:
- RenderObjectToWidgetAdapter通過建構函式獲取了外部傳入的widget和renderObject(rootRenderObject),初始化一個rootWidget的例項物件,並把外部傳入的widget掛載到rootWidget上
- RenderObjectToWidgetAdapter的例項物件通過
createElement()
建立一個RenderObjectToWidgetElement的例項物件作為rootElement,rootElement持有rootWidget和rootRenderObject. - rootElement的呼叫
mount()
函式,在父類的實現中,會通過一個getter方法,獲取當前rootElement持有的widget,也就是rootWidget,然後rootWidget呼叫createRenderObject()
方法返回從外部獲取的renderObject,並掛載到rootElement,作為rootRenderObject - 通過上面的步驟,三棵樹的根節點就建立好,並建立了相互的引用關係
子節點是如何初始化生成的,簡要如下:
- rootElement在
mount()
函式最後,繼續呼叫了自己的私有函式_rebuild()
,通過原始碼可以看到,rootElement的_child就被構造完成。
void _rebuild() {
try {
_child = updateChild(_child, widget.child, _rootChildSlot);
assert(_child != null);
} catch (exception, stack) {
...
final Widget error = ErrorWidget.builder(details);
_child = updateChild(null, error, _rootChildSlot);
}
}
複製程式碼
- 這裡的
updateChild()
函式是一個重點,看其原始碼可以知道,這個函式主要是通過引數child和newWidget的組合判斷返回結果,如下:
newWidget == null | newWidget != null | |
---|---|---|
child == null | Return:null. | Return new [Element] |
child != null | 移除child,Return:null | return child or new [Element] |
由於這裡我們只討論第一次初始化,所以這裡傳入的child == null, newWidget是已掛載到rootWidget上的widget,所以會通過inflateWidget()
函式初始化一個newChild,並返回掛載到rootElement上。注意,這裡的newChild初始化後,並不是直接返回的,而是繼續呼叫其mount()函式,通過super.mount()
,這裡需要注意的是,ComponentElement和RenderObjectElement是不一樣的,RenderObjectElement會初始化一個RenderObject掛載到Element上。整個過程就是這樣不斷的查詢widget的child構造出相對應的element和renderObject.
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
...
Element newChild;
if (child != null) {
...
} else {
newChild = inflateWidget(newWidget, newSlot);
}
...
return newChild;
}
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {
...
final Element newChild = newWidget.createElement();
...
newChild.mount(this, newSlot);
...
return newChild;
}
複製程式碼
結束
腦袋嗡嗡的... 文章篇幅有限,到這裡就結束了,下次再繼續聊Flutter. 有錯誤的地方,請各位大佬不吝指正,共同學習,一起提高。