Widget、RenderObject 與 Element
我們在學習 Flutter 的時候,可能經常看到三個名詞: Widget、RenderObject 和 Element ,弄懂這幾個概念可能也是入門 Flutter 框架原理的第一步。
一、 Widget
在 Flutter 中,萬物皆是 Widget,無論是可見的還是功能型的,那麼 Widget 究竟是什麼呢?
按照慣例,先看官方文件。
- Widget 的作用是用來儲存 Element 的配置資訊的。
- Widget 本身是不可變的。
- Element 根據 Widget 裡面儲存的配置資訊來管理渲染樹。
- Widget 可以多次的插入到 Widget 樹中,每插入一次,Element 都要重新裝載一遍 Widget 。
- Widget 裡面的 key 屬性用來決定依賴這個 Widget 的 Element 在 Element 樹中是更新還是移除。
接下來看一下 Widget 的定義。
abstract class Widget {
const Widget({ this.key });
final Key key;
@protected
Element createElement();
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
複製程式碼
通過上面 Widget 的定義,可以看到有兩個重要的方法,一個是通過 createElement 來建立 Element 物件的,一個是根據 key 來決定更新行為的 canUpdate 方法。
以 Opacity 為例,Opacity 做為一個 Widget ,只儲存另一個配置資訊:opacity,這個屬性決定了透明度,範圍在 0 到 1 之間。
Opacity 既然做為一個 Widget,肯定是 Widget 的子類,其繼承關係如下:
Opacity → SingleChildRenderObjectWidget → RenderObjectWidget → Widget
複製程式碼
Opacity 的定義如下:
class Opacity extends SingleChildRenderObjectWidget {
const Opacity({
Key key,
@required this.opacity,
this.alwaysIncludeSemantics = false,
Widget child,
}) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
assert(alwaysIncludeSemantics != null),
super(key: key, child: child);
final double opacity;
@override
RenderOpacity createRenderObject(BuildContext context) {
return RenderOpacity(
opacity: opacity,
alwaysIncludeSemantics: alwaysIncludeSemantics,
);
}
@override
void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
renderObject
..opacity = opacity
..alwaysIncludeSemantics = alwaysIncludeSemantics;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DoubleProperty('opacity', opacity));
properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
}
}
複製程式碼
通過上面的程式碼,可以看到 opacity 是 final 型別的屬性,只能做為建構函式引數傳遞進去,不可改變,因此如果要更新這個屬性,必須新建一個 Opcity 物件,這也是為什麼我們程式碼裡的 Widget build(BuildContext context) 方法裡面每次 build 都會建立新的物件例項的原因。
二、RenderObject
- RenderObject 是做為渲染樹中的物件存在的。
- RenderObject 不會定義約束關係,也就是不會對子控制元件的佈局位置、大小等進行管理。
- RenderObject 中有一個 parentData 屬性,這個屬性用來儲存其孩子節點的特定資訊,如子節點位置,這個屬性對其孩子是透明的。
RenderObject 的定義如下
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
ParentData parentData;
Constraints _constraints;
void layout(Constraints constraints, { bool parentUsesSize = false }) {
}
void paint(PaintingContext context, Offset offset) { }
void performLayout();
void markNeedsPaint() {
}
}
複製程式碼
通過以上定義,可以看出,RenderObject 的主要作用就是繪製和佈局的。
那麼這個 RenderObject 是哪裡來的呢?是在 Widget 裡面建立的。如上面的 Opacity 中重寫了 createRenderObject 方法來建立 RenderOpacity 物件。
@override
RenderOpacity createRenderObject(BuildContext context) {
return RenderOpacity(
opacity: opacity,
alwaysIncludeSemantics: alwaysIncludeSemantics,
);
}
複製程式碼
在 RenderOpacity 內部實現了佈局、點選檢測和大小計算等功能。
三、 Element
- Element 可以理解為是其關聯的 Widget 的例項,並且關聯在 Widget 樹的特定位置上。
- 由於 Widget 是不可變的,因此一個 Widget 可以同時用來配置多個子 Widget 樹,而 Element 就用來代表特定位置的 Widget 。
- Widget 是不可變的,但是 Element 是可變的。
- 一些 Element 只能有一個子節點,如 Container,Opacity,Center 還有一些可以有多個子節點,如 Column ,Row 和 ListView 等。
Element 的 生命週期:
-
Flutter framework 通過 Widget.createElement 來建立一個 element 。
-
每當 Widget 建立並插入到 Widget 樹中時,framework 就會通過 mount 方法來把這個 widget 建立並關聯的 element 插入到 element 樹中(其父 element 會給出一個位置)。
-
通過 attachRenderObject 方法來將 render objects 來關聯到 render 樹上,這時可以認為這個 widget 已經顯示在螢幕上了。
-
每當執行了 rebuid 方法,widget 代表的配置資訊改變時(建立了一個新的 widget),framewrok 就會呼叫這個新的 widget 的 update 方法(新的 widget 的 和老的 widget 有相同的 runtimeType 和 key,如果不同,就要先 unmounting 然後重新裝載 widget)。
-
當 element 的祖先想要移除一個子 element 時,可以通過 deactivateChild 方法,先把這個 element 從 樹中移除,然後將這個 element 加入到一個“不活躍元素列表”中,接著 framework 就會將這個 element 從螢幕移除(當下一個渲染幀到來這個 element 依然不活躍)。
由於 是在 Widget 中建立了Element,類似於 Widget 的繼承關係,Element 的繼承關係如下:
SingleChildRenderObjectElement → RenderObjectElement → Element
複製程式碼
接著看一下 Opacity 中,如何建立一個 Element 。 Opacity 繼承自 SingleChildRenderObjectElement,在 SingleChildRenderObject 中建立了 Element
@override
SingleChildRenderObjectElement createElement() => new SingleChildRenderObjectElement(this);
複製程式碼
在 RenderObjectElement 中提供了 mount 方法
abstract class RenderObjectElement extends Element {
RenderObjectElement(RenderObjectWidget widget) : super(widget);
RenderObject _renderObject;
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
}
複製程式碼
通過上面的程式碼,我們能夠發現,Element 中通過 widget.createRenderObject 方法也拿到了 RenderObject 物件,因此 Element 其實是同時包含 RenderObject 和 Widget 。
mount 方法會將 element 插入到 element 樹中,mount 中還會呼叫 attachRenderObject 方法。
abstract class RenderObjectElement extends Element {
@override
void attachRenderObject(dynamic newSlot) {
_slot = newSlot;
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
if (parentDataElement != null)
_updateParentData(parentDataElement.widget);
}
}
複製程式碼
在這個方法裡,通過 _findAncestorRenderObjectElement 方法, 找到了Element樹上的祖先Element,如果祖先不為空,就呼叫insertChildRenderObject方法,這個方法的意思就是把renderObject的child替換成newSlot,然後通過 _updateParentData 用於更新佈局資料的一些資訊。
總結
上面只是簡單介紹了一下 Flutter 中的 Widget 、RenderObject 和 Element 中的概念,而 Widget,Element和RenderObject體系是Flutter框架的核心 至於內部原理以及如果工作的,需要結合 Flutter 框架結構執行原理來看,這樣才能更好的理解這些概念。
參考:
Flutter, what are Widgets, RenderObjects and Elements
歡迎關注「Flutter 程式設計開發」微信公眾號 。