- 文章信息 - Author: 李俊才 (jcLee95)
Visit me at: https://jclee95.blog.csdn.net
Email: 291148484@163.com.
Shenzhen China
Address of this article:https://blog.csdn.net/qq_28550263/article/details/132259681
【介紹】:本文記錄閱讀與分析Flutter源碼 - Widget類源碼分析。
目 錄
- 1. 概述
- 2. 屬性
- 3. 方法
- 3.1 createElement()
- 3.2 canUpdate(Widget oldWidget, Widget newWidget)
- 3.3 debugFillProperties(DiagnosticPropertiesBuilder properties)
- 3.4 _debugConcreteSubtype(Widget widget)
- 3.5 其它
- operator ==(Object other) 和 get hashCode
- toStringShort()
- 4. 繼承關系
- 5. Widget樹
- Widget樹的概念
- Widget樹的特點
- F. 附錄
1. 概述
Widget類是Flutter框架中的核心類之一,用于描述用戶界面的一部分。它是一個不可變的描述,可以被實例化為元素(Element),后者負責管理底層的渲染樹。
Widget是Flutter框架的基礎,用于描述用戶界面的一部分,不可變且無狀態。它有一些用于實例化元素、診斷調試和比較的方法和屬性,同時它的子類可以是StatelessWidget或StatefulWidget,用于構建靜態或有狀態的用戶界面部分。
這部分源碼見附錄F-1。
2. 屬性 2.1 keykey屬性控制一個widget如何替換樹中的另一個widget。如果兩個widget的runtimeType和key屬性分別通過operator==比較是相等的,那么新的widget將通過更新底層元素來替換舊的widget。否則,舊的元素會從樹中移除,新的widget會被實例化為元素,然后插入樹中。
3. 方法 3.1 createElement()該方法將配置實例化為一個具體的元素(Element)。一個widget可以在樹中被包含零次或多次,每次被包含在樹中時,都會被實例化為一個元素。
3.2 canUpdate(Widget oldWidget, Widget newWidget)該方法用于判斷一個新的widget是否可以用來更新一個已有元素的配置。根據runtimeType和key屬性的比較來判斷兩個widget是否可以更新。
3.3 debugFillProperties(DiagnosticPropertiesBuilder properties)debugFillProperties(DiagnosticPropertiesBuilder properties):向診斷信息屬性構建器添加屬性,用于調試和診斷。在子類中可以重寫以提供更多的調試信息。
3.4 _debugConcreteSubtype(Widget widget)返回一個數值,表示特定Widget子類型的具體編碼。用于在熱重載時判斷已掛載元素的配置是否被修改。
3.5 其它operator ==(Object other) 和 get hashCode
實現運算符”==“用于判斷兩個widget是否相等,以及計算它們的哈希值。
toStringShort()
返回此widget的簡短文本描述,通常是它的runtimeType和key的組合。
4. 繼承關系 4.1 Widget的父類Widget 是一個抽象類,繼承自 DiagnosticableTree(這個類表示診斷樹,主要用于提供調試信息),因此繼承了一些用于調試和診斷的方法和屬性。
4.2 Widget的子類 4.2.1 StatefulWidget 和 StatelessWidgetWidget 本身沒有可變狀態,它的所有字段都必須是 final
。如果需要關聯可變狀態,應該使用 StatefulWidget,后者在被實例化為元素并添加到樹中時會創建一個State對象。Widget的子類可以是 StatelessWidget(始終以相同的方式構建)或 StatefulWidget(可以在其生命周期內多次構建)。
StatefulWidget: 這是一個帶有可變狀態的 Widget 類別。它由兩部分組成:一個是不可變的描述部分(Widget),另一個是可變的狀態部分(State)。StatefulWidget 實例在構建過程中可以改變其狀態,當狀態發生變化時,相關的 State 對象會被重新構建以更新界面。適用于有變化狀態的部分,比如用戶輸入、數據加載等。
StatelessWidget: 這是一個不可變的 Widget 類別,其描述和外觀在整個生命周期內保持不變。StatelessWidget 實例在構建時不會持有可變狀態,因此適用于不需要變化的 UI 部分,如圖標、文本等。
Widget可以被多次包含在樹中,每次都會被實例化為元素。如果某個 widget 在樹中出現多次,它將被多次實例化。
4.2.2 RenderObjectWidgetRenderObjectWidget是Flutter渲染引擎的一部分,表示屏幕上的可見對象,它又有LeafRenderObjectWidget、SingleChildRenderObjectWidget、MultiChildRenderObjectWidget這三個子類。
- LeafRenderObjectWidget: 這是一種將 RenderObject 無需管理子元素的 Widget 類別。它通常用于將自定義的繪制邏輯封裝為 Widget,然后通過構建 RenderObject 進行繪制。
- SingleChildRenderObjectWidget: 這是一種管理單個子元素的 RenderObjectWidget 類別。它會創建一個單一的子元素,并將其作為子節點傳遞給 RenderObject 進行渲染。
- MultiChildRenderObjectWidget: 這是一種管理多個子元素的 RenderObjectWidget 類別。它會創建多個子元素,并將它們作為子節點傳遞給 RenderObject 進行渲染。比如 Stack、Column 和 Row 等都是 MultiChildRenderObjectWidget 的子類。
ProxyWidget提供一種方式來包裝 Widgets,以實現特定的功能。它是一個具有子Widget的Widget,而非新的Widget
- ParentDataWidget: 這是一個用于修改子 Widget 布局約束的 ProxyWidget 子類。它在渲染樹中修改子元素的布局信息,例如 Positioned 和 Align 等都是 ParentDataWidget 的子類,用于指定子元素的位置和對齊方式。
- InheritedWidget: 這是一種特殊類型的 ProxyWidget,它允許在 Widget 樹中向下傳遞共享的數據,而不需要顯式地傳遞。當 InheritedWidget 更新時,其子孫節點會自動重新構建。適用于需要在多個部分之間共享數據的情況,如主題、語言等。
Widget樹的概念
在Flutter中,Widget 樹是指由各種不同類型的Widget構成的層次結構。每個Widget描述了用戶界面的一部分,可以是一個簡單的元素,也可以是一個復雜的組合。這些Widget通過嵌套關系形成了一個樹狀結構,被稱為Widget樹。這種嵌套關系定義了界面中各個部分的排列和組織方式。
Widget樹是構建用戶界面的基本模型。當 Flutter 應用程序運行時,它會從一個 根Widget 開始,然后逐級構建出整個界面。每個Widget都有一個與之相關聯的Element,負責管理底層的渲染樹。渲染樹最終會被轉化為可視的UI元素,顯示在屏幕上。
Widget樹的特點
嵌套關系: Widget 樹的節點由各種不同類型的 Widget 組成,這些 Widget 可以嵌套在彼此內部,形成層次結構。
不可變性: Widget 本身是 不可變 的,一旦創建就不能再進行修改。如果需要更新界面,通常是通過創建新的Widget來替換舊的Widget。
構建方式: 構建 Widget 樹通常是通過構建方法來完成的。在構建方法中,你可以創建和組合不同的 Widget,從而構建出整個界面。
熱重載: Flutter支持熱重載,這意味著你可以在不重新啟動應用程序的情況下快速修改和查看界面的變化。在熱重載期間,Flutter會比較新舊Widget樹的差異,并盡可能地保留應用程序的狀態。
響應式: Flutter的界面是響應式的,意味著當數據發生變化時,相關的Widget會自動更新。這是通過在StatefulWidget 中管理可變狀態來實現的。
F. 附錄 F.1 Widget類源碼abstract class Widget extends DiagnosticableTree {/// Initializes [key] for subclasses.const Widget({ this.key });/// Controls how one widget replaces another widget in the tree.////// If the [runtimeType] and [key] properties of the two widgets are/// [operator==], respectively, then the new widget replaces the old widget by/// updating the underlying element (i.e., by calling [Element.update] with the/// new widget). Otherwise, the old element is removed from the tree, the new/// widget is inflated into an element, and the new element is inserted into the/// tree.////// In addition, using a [GlobalKey] as the widget's [key] allows the element/// to be moved around the tree (changing parent) without losing state. When a/// new widget is found (its key and type do not match a previous widget in/// the same location), but there was a widget with that same global key/// elsewhere in the tree in the previous frame, then that widget's element is/// moved to the new location.////// Generally, a widget that is the only child of another widget does not need/// an explicit key.////// See also:////// * The discussions at [Key] and [GlobalKey].final Key? key;/// Inflates this configuration to a concrete instance.////// A given widget can be included in the tree zero or more times. In particular/// a given widget can be placed in the tree multiple times. Each time a widget/// is placed in the tree, it is inflated into an [Element], which means a/// widget that is incorporated into the tree multiple times will be inflated/// multiple times.Element createElement();/// A short, textual description of this widget.String toStringShort() {final String type = objectRuntimeType(this, 'Widget');return key == null ? type : '$type-$key';}void debugFillProperties(DiagnosticPropertiesBuilder properties) {super.debugFillProperties(properties);properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;} bool operator ==(Object other) => super == other; int get hashCode => super.hashCode;/// Whether the `newWidget` can be used to update an [Element] that currently/// has the `oldWidget` as its configuration.////// An element that uses a given widget as its configuration can be updated to/// use another widget as its configuration if, and only if, the two widgets/// have [runtimeType] and [key] properties that are [operator==].////// If the widgets have no key (their key is null), then they are considered a/// match if they have the same type, even if their children are completely/// different.static bool canUpdate(Widget oldWidget, Widget newWidget) {return oldWidget.runtimeType == newWidget.runtimeType&& oldWidget.key == newWidget.key;}// Return a numeric encoding of the specific `Widget` concrete subtype.// This is used in `Element.updateChild` to determine if a hot reload modified the// superclass of a mounted element's configuration. The encoding of each `Widget`// must match the corresponding `Element` encoding in `Element._debugConcreteSubtype`.static int _debugConcreteSubtype(Widget widget) {return widget is StatefulWidget ? 1 :widget is StatelessWidget ? 2 :0;}
}