跟我學flutter:細細品Widget(一)Widget&Element初識

語言: CN / TW / HK

一起養成寫作習慣!這是我參與「掘金日新計劃 · 4 月更文挑戰」的第3天,點選檢視活動詳情

前言

Everything's a widget!

Widget

Flutter 中 Widget是一個“描述一個UI元素的配置資訊”,Widget就是接受元素,而不是真是繪製的顯示元素。 類比原生的Android開發,Widget更像是負責UI配置的xml檔案,而非負責繪製元件的View。 當一個Widget狀態發生變化時,Widget就會重新呼叫build()函式來返回控制元件的描述,過程中Flutter框架會與之前的Widget進行比較,確保實現渲染樹中最小的變動來保證效能和穩定性。換句話說,當Widget發生改變時,渲染樹只會更新其中的一小部分而非全部重新渲染。

原始碼

```bash @immutable abstract class Widget extends DiagnosticableTree { const Widget({ this.key });

final Key? key;

@protected @factory Element createElement();

@override String toStringShort() { final String type = objectRuntimeType(this, 'Widget'); return key == null ? type : '$type-$key'; }

@override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense; }

@override @nonVirtual bool operator ==(Object other) => super == other;

@override @nonVirtual int get hashCode => super.hashCode;

static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; } ... } ``` 圖: 在這裡插入圖片描述

@immutable

@immutable widget中的屬性時不可變的,如果有可變的你需要放在state中。

如果屬性發生變更flutter則會重新構建Widget樹,一旦 Widget 自己的屬性變了自己就會被替換。 如你在開發過程中會有如下提示:This class (or a class that this class inherits from) is marked as '@immutable', but one or more of its instance fields aren't final:

key

主要用於控制當 Widget 更新時,對應的 Element 如何處理 (是更新還是新建)。若某 Widget 是其「Parent Widget」唯一的子節點時,一般不用設定 key

LocalKey

LocalKey是diff演算法的核心所在,用做Element和Widget的比較。常用子類有以下幾個:

ValueKey:以一個數據為作為key,比如數字、字元等。 ObjectKey:以Object物件作為Key。 UniqueKey:可以保證key的唯一性,如果使用這個型別的key,那麼Element物件將不會被複用。 PageStorageKey:用於儲存頁面滾動位置的key。

GlobalKey

每個globalkey都是一個在整個應用內唯一的key。globalkey相對而言是比較昂貴的,如果你並不需要globalkey的某些特性,那麼可以考慮使用Key、ValueKey、ObjectKey或UniqueKey。 他有兩個用途:

  1. 允許widget在應用程式中的任何位置更改其parent而不丟失其狀態。應用場景:在兩個不同的螢幕上顯示相同的widget,並保持狀態相同。
  2. 可以獲取對應Widget的state物件:

createElement

一個 widget 可以對應多個Element

canUpdate

控制一個widget如何替換樹中另一個widget。如果兩個widget的runtimeType與key相同,則表示新的widget將替換舊的widget,並呼叫Element.update更新Element;否則舊的element將從樹中移出,新的element插入樹中。

Widget在重新build的時候,是增量更新的,而不是全部更新 runtimeType就是這個widget的型別

Widget類大家族

在這裡插入圖片描述

簡述(後面文章將展開講解): - StatelessWidget:無狀態Widget - StatefulWidget:有狀態Widget,值得注意的是StatefulWidget是不可變的,變化的狀態在。 - ProxyWidget:其有2個比較重要的子類, ParentDataWidget和InheritedWidget - RenderObjectWidget:持有RenderObject物件的Widget,RenderObject是完成介面的佈局、測量與繪製,像Padding,Table,Align都是它的子類

Widget的建立可以做到複用,通過const修飾,否則setState後,Widget重新被建立了(Element不會重建)

Element

通過Widget Tree,會生成一系列Element Tree,其主要功能如下:

  1. 維護這棵Element Tree,根據Widget Tree的變化來更新Element Tree,包括:節點的插入、更新、刪除、移動等
  2. Element 是 Widget 和 RenderObject 的粘合劑,根據 Element 樹生成 Render 樹(渲染樹)

Element類大家族

在這裡插入圖片描述

兩大類:

簡述(後面文章將展開講解):

ComponentElement

組合類Element。這類Element主要用來組合其他更基礎的Element,得到功能更加複雜的Element。開發時常用到的StatelessWidget和StatefulWidget相對應的Element:StatelessElement和StatefulElement,即屬於ComponentElement。

RenderObjectElement

渲染類Element,對應Renderer Widget,是框架最核心的Element。RenderObjectElement主要包括LeafRenderObjectElement,SingleChildRenderObjectElement,和MultiChildRenderObjectElement。其中,LeafRenderObjectElement對應的Widget是LeafRenderObjectWidget,沒有子節點;SingleChildRenderObjectElement對應的Widget是SingleChildRenderObjectWidget,有一個子節點;MultiChildRenderObjectElement對應的Widget是MultiChildRenderObjecWidget,有多個子節點。

Element生命週期

Element有4種狀態:initial,active,inactive,defunct。其對應的意義如下:

  • initial:初始狀態,Element剛建立時就是該狀態。
  • active:啟用狀態。此時Element的Parent已經通過mount將該Element插入Element Tree的指定的插槽處(Slot),Element此時隨時可能顯示在螢幕上。
  • inactive:未啟用狀態。當Widget Tree發生變化,Element對應的Widget發生變化,同時由於新舊Widget的Key或者的RunTimeType不匹配等原因導致該Element也被移除,因此該Element的狀態變為未啟用狀態,被從螢幕上移除。並將該Element從Element Tree中移除,如果該Element有對應的RenderObject,還會將對應的RenderObject從Render Tree移除。但是,此Element還是有被複用的機會,例如通過GlobalKey進行復用。
  • defunct:失效狀態。如果一個處於未啟用狀態的Element在當前幀動畫結束時還是未被複用,此時會呼叫該Element的unmount函式,將Element的狀態改為defunct,並對其中的資源進行清理。

Element4種狀態間的轉換關係如下圖所示:

在這裡插入圖片描述