2、Widget、Element、RenderObjectapp
7、Paint 繪製(2)post
9、Flutter 小實踐this
在監聽 vsync 信號回調時會調用 drawFrame 函數spa
(1) drawFramedebug
void drawFrame() {
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
superd.drawFrame();
}
複製代碼
在 drawFrame 方法中,則調用了 buildScope 則是 Build 流程
(2) buildScope
void buildScope(Element context, [ VoidCallback callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
_dirtyElements[index].rebuild();
index += 1;
}
}
複製代碼
遍歷 _dirtyElements, 從新 rebuild
(3)Element -> rebuild
void rebuild() {
if (!_active || !_dirty)
return;
performRebuild();
}
複製代碼
rebuild 方法中主要是調用了 performRebuild
(4) Element -> performRebuild
這個類是由具體的類繼承的,如以 Text widget 爲例, 其 對應的 element 是 StatelessElement, 而 StatelessElement 繼承 ComponentElement
(5)ComponentElement --> performRebuild
@override
void performRebuild() {
Widget built;
built = build();
_child = updateChild(_child, built, slot);
}
複製代碼
(5)build
StatelessElement 重寫 了 componentElement 的build 的方法
@override
Widget build() => widget.build(this);
複製代碼
因而可知,build 方法實際調用的就是咱們 平時寫組件時的 build,這個方法會返回新的 widget 樹,新的widget 樹有什麼用呢?且看接下來調用的 updateChild
(6)updateChild
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null) {
deactivateChild(child); // 第一種狀況
}
return null;
}
if (child != null) {
if (child.widget == newWidget) {
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot); // 第二種狀況
}
return child;
}
if (Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot); // 第三種狀況
}
child.update(newWidget);
return child;
}
deactivateChild(child);
}
return inflateWidget(newWidget, newSlot); // 第四種狀況
}
複製代碼
這個方法主要是涉及 Element 的更新邏輯,更新規則以下
第一種狀況: build出來的widget等於null,也就是newWidget 是null, 說明這個控件被刪除了,child Element能夠被刪除了
第二種狀況:child的 widget 和新 build 出來的同樣,則判斷 slot(父級設置的信息,以定義此子級在其父級的子級列表中的位置) 是否一致,不一致則更新,Element仍是舊的Element
第三種狀況:canUpdate(判斷key值和 runtimeType 是否一致) 爲 true 時, 更新 Element 便可
第四種狀況:以上三種狀況都不知足時,建立新的Element
(7) inflateWidget
這個方法主要是用於建立新的 Element
Element inflateWidget(Widget newWidget, dynamic newSlot) {
final Key key = newWidget.key;
if (key is GlobalKey) {
// 尋找有沒有可用的Element
final Element newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
newChild._activateWithParent(this, newSlot);
final Element updatedChild = updateChild(newChild, newWidget, newSlot);
return updatedChild;
}
}
// 建立 Element
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
}
複製代碼
建立了新的 Element 以後,則會調用 elment 的 mount 方法,完成 element 樹的掛載,其中,mount 方法是由具體的子類實現的,ComponentElement 類的 mount 方法會遞歸遍歷子節點,調用子節點的 rebuild方法: _firstBuild -> rebuild -> performRebuild ,而 RenderElement 類的 mount 方法 則會調用 widget 的 createRenderObject 建立對應的 renderObject, 並renderObject 插到對應的 render樹上。
(8) ComponentElement --> mount
循環遍歷子節點
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_firstBuild();
}
複製代碼
(9) RenderElement --> mount
建立 renderObject 而且 將 renderObject 插入到 render 樹上
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this); // 建立renderObject
attachRenderObject(newSlot); // 將renderObject 插入到render樹上
_dirty = false;
}
複製代碼
在Vsync 信號回調時會觸發 build 流程,可是首幀渲染並無等待 Vsync 信號的回調,即在 app 啓動初始化時,element 樹又是怎樣構建的呢
(1) runApp
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
複製代碼
在 runApp 方法中,完成了 Binding 的初始化後,調用了 attachRootWidget 方法
(2)attachRootWidget
在這個方法中,經過 RenderObjectToWidgetAdapter 建立根elememnt _renderViewElement, 其中 renderView 是根 renderObject
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement);
}
複製代碼
(3)attachToRenderTree
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
owner.buildScope(element, () {
element.mount(null, null);
});
return element;
}
複製代碼
其實這裏也是將 根 element 掛載同時遍歷建立子節點,這個流程跟上面分析的build流程是相似的