国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

Flutter實戰(zhàn) Element與BuildContext

2021-03-09 14:42 更新

#14.2.1 Element

在“Widget 簡介”一節(jié),我們介紹了 Widget 和 Element 的關(guān)系,我們知道最終的 UI 樹其實是由一個個獨立的 Element 節(jié)點構(gòu)成。我們也說過組件最終的 Layout、渲染都是通過RenderObject來完成的,從創(chuàng)建到渲染的大體流程是:根據(jù) Widget 生成 Element,然后創(chuàng)建相應(yīng)的RenderObject并關(guān)聯(lián)到Element.renderObject屬性上,最后再通過RenderObject來完成布局排列和繪制。

Element 就是 Widget 在 UI 樹具體位置的一個實例化對象,大多數(shù) Element 只有唯一的renderObject,但還有一些 Element 會有多個子節(jié)點,如繼承自RenderObjectElement的一些類,比如MultiChildRenderObjectElement。最終所有 Element 的 RenderObject 構(gòu)成一棵樹,我們稱之為”Render Tree“即”渲染樹“。總結(jié)一下,我們可以認為 Flutter的UI 系統(tǒng)包含三棵樹:Widget 樹、Element 樹、渲染樹。他們的依賴關(guān)系是:Element 樹根據(jù) Widget 樹生成,而渲染樹又依賴于Element 樹,如圖14-0所示。

圖14-0

現(xiàn)在我們重點看一下 Element,Element 的生命周期如下:

  1. Framework 調(diào)用Widget.createElement 創(chuàng)建一個 Element 實例,記為element
  2. Framework 調(diào)用 element.mount(parentElement,newSlot) ,mount 方法中首先調(diào)用element所對應(yīng) Widget 的createRenderObject方法創(chuàng)建與element相關(guān)聯(lián)的 RenderObject 對象,然后調(diào)用element.attachRenderObject方法將element.renderObject添加到渲染樹中插槽指定的位置(這一步不是必須的,一般發(fā)生在 Element 樹結(jié)構(gòu)發(fā)生變化時才需要重新attach)。插入到渲染樹后的element就處于“active”狀態(tài),處于“active”狀態(tài)后就可以顯示在屏幕上了(可以隱藏)。
  3. 當有父 Widget 的配置數(shù)據(jù)改變時,同時其State.build返回的 Widget 結(jié)構(gòu)與之前不同,此時就需要重新構(gòu)建對應(yīng)的 Element 樹。為了進行 Element 復用,在 Element 重新構(gòu)建前會先嘗試是否可以復用舊樹上相同位置的 element,element 節(jié)點在更新前都會調(diào)用其對應(yīng) Widget 的canUpdate方法,如果返回true,則復用舊 Element,舊的 Element 會使用新 Widget 配置數(shù)據(jù)更新,反之則會創(chuàng)建一個新的 Element。Widget.canUpdate主要是判斷newWidgetoldWidgetruntimeTypekey是否同時相等,如果同時相等就返回true,否則就會返回false。根據(jù)這個原理,當我們需要強制更新一個 Widget 時,可以通過指定不同的 Key 來避免復用。
  4. 當有祖先 Element 決定要移除element 時(如 Widget 樹結(jié)構(gòu)發(fā)生了變化,導致element對應(yīng)的 Widget 被移除),這時該祖先 Element 就會調(diào)用deactivateChild 方法來移除它,移除后element.renderObject也會被從渲染樹中移除,然 后 Framework 會調(diào)用element.deactivate 方法,這時element狀態(tài)變?yōu)椤癷nactive”狀態(tài)。
  5. “inactive”態(tài)的 element 將不會再顯示到屏幕。為了避免在一次動畫執(zhí)行過程中反復創(chuàng)建、移除某個特定 element,“inactive”態(tài)的 element 在當前動畫最后一幀結(jié)束前都會保留,如果在動畫執(zhí)行結(jié)束后它還未能重新變成“active”狀態(tài),F(xiàn)ramework 就會調(diào)用其unmount方法將其徹底移除,這時 element 的狀態(tài)為defunct,它將永遠不會再被插入到樹中。
  6. 如果element要重新插入到 Element 樹的其它位置,如elementelement的祖先擁有一個GlobalKey(用于全局復用元素),那么 Framework 會先將 element 從現(xiàn)有位置移除,然后再調(diào)用其activate方法,并將其renderObject重新 attach 到渲染樹。

看完 Element 的生命周期,可能有些讀者會有疑問,開發(fā)者會直接操作 Element 樹嗎?其實對于開發(fā)者來說,大多數(shù)情況下只需要關(guān)注 Widget 樹就行,F(xiàn)lutter 框架已經(jīng)將對 Widget 樹的操作映射到了Element樹上,這可以極大的降低復雜度,提高開發(fā)效率。但是了解 Element 對理解整個 Flutter UI 框架是至關(guān)重要的,F(xiàn)lutter 正是通過 Element 這個紐帶將 Widget 和 RenderObject 關(guān)聯(lián)起來,了解 Element 層不僅會幫助讀者對 Flutter UI 框架有個清晰的認識,而且也會提高自己的抽象能力和設(shè)計能力。另外在有些時候,我們必須得直接使用 Element 對象來完成一些操作,比如獲取主題 Theme 數(shù)據(jù),具體細節(jié)將在下文介紹。

#14.2.2 BuildContext

我們已經(jīng)知道,StatelessWidgetStatefulWidgetbuild方法都會傳一個BuildContext對象:

Widget build(BuildContext context) {}

我們也知道,在很多時候我們都需要使用這個context 做一些事,比如:

Theme.of(context) //獲取主題
Navigator.push(context, route) //入棧新路由
Localizations.of(context, type) //獲取Local
context.size //獲取上下文大小
context.findRenderObject() //查找當前或最近的一個祖先RenderObject

那么BuildContext到底是什么呢,查看其定義,發(fā)現(xiàn)其是一個抽象接口類:

abstract class BuildContext {
    ...
}

那這個context對象對應(yīng)的實現(xiàn)類到底是誰呢?我們順藤摸瓜,發(fā)現(xiàn)build調(diào)用是發(fā)生在StatelessWidgetStatefulWidget對應(yīng)的StatelessElementStatefulElementbuild方法中,以StatelessElement為例:

class StatelessElement extends ComponentElement {
  ...
  @override
  Widget build() => widget.build(this);
  ...
}

發(fā)現(xiàn)build傳遞的參數(shù)是this,很明顯!這個BuildContext就是StatelessElement。同樣,我們同樣發(fā)現(xiàn)StatefulWidgetcontextStatefulElement。但StatelessElementStatefulElement本身并沒有實現(xiàn)BuildContext接口,繼續(xù)跟蹤代碼,發(fā)現(xiàn)它們間接繼承自Element類,然后查看Element類定義,發(fā)現(xiàn)Element類果然實現(xiàn)了BuildContext接口:

class Element extends DiagnosticableTree implements BuildContext {
    ...
}

至此真相大白,BuildContext就是 widget 對應(yīng)的Element,所以我們可以通過contextStatelessWidgetStatefulWidgetbuild方法中直接訪問Element對象。我們獲取主題數(shù)據(jù)的代碼Theme.of(context)內(nèi)部正是調(diào)用了Element的dependOnInheritedWidgetOfExactType()方法。

思考題:為什么 build 方法的參數(shù)不定義成 Element 對象,而要定義成 BuildContext ?

#進階

我們可以看到 Element 是 Flutter UI 框架內(nèi)部連接 widget 和RenderObject的紐帶,大多數(shù)時候開發(fā)者只需要關(guān)注 widget 層即可,但是 widget 層有時候并不能完全屏蔽Element細節(jié),所以 Framework 在StatelessWidgetStatefulWidget中通過build方法參數(shù)又將Element對象也傳遞給了開發(fā)者,這樣一來,開發(fā)者便可以在需要時直接操作Element對象。那么現(xiàn)在筆者提兩個問題,請讀者先自己思考一下:

  1. 如果沒有 widget 層,單靠Element層是否可以搭建起一個可用的 UI 框架?如果可以應(yīng)該是什么樣子?
  2. Flutter UI 框架能不做成響應(yīng)式嗎?

對于問題1,答案當然是肯定的,因為我們之前說過 widget 樹只是Element樹的映射,我們完全可以直接通過 Element 來搭建一個 UI 框架。下面舉一個例子:

我們通過純粹的 Element 來模擬一個StatefulWidget的功能,假設(shè)有一個頁面,該頁面有一個按鈕,按鈕的文本是一個9位數(shù),點擊一次按鈕,則對9個數(shù)隨機排一次序,代碼如下:

class HomeView extends ComponentElement{
  HomeView(Widget widget) : super(widget);
  String text = "123456789";


  @override
  Widget build() {
    Color primary=Theme.of(this).primaryColor; //1
    return GestureDetector(
      child: Center(
        child: FlatButton(
          child: Text(text, style: TextStyle(color: primary),),
          onPressed: () {
            var t = text.split("")..shuffle();
            text = t.join();
            markNeedsBuild(); //點擊后將該Element標記為dirty,Element將會rebuild
          },
        ),
      ),
    );
  }
}

  • 上面build方法不接收參數(shù),這一點和在StatelessWidgetStatefulWidgetbuild(BuildContext)方法不同。代碼中需要用到BuildContext的地方直接用this代替即可,如代碼注釋1處Theme.of(this)參數(shù)直接傳this即可,因為當前對象本身就是Element實例。
  • text發(fā)生改變時,我們調(diào)用markNeedsBuild()方法將當前 Element 標記為 dirty 即可,標記為 dirty 的 Element 會在下一幀中重建。實際上,State.setState()在內(nèi)部也是調(diào)用的markNeedsBuild()方法。
  • 上面代碼中 build 方法返回的仍然是一個 widget,這是由于 Flutter 框架中已經(jīng)有了 widget 這一層,并且組件庫都已經(jīng)是以 widget 的形式提供了,如果在 Flutter 框架中所有組件都像示例的HomeView一樣以Element形式提供,那么就可以用純Element來構(gòu)建UI了HomeView的 build 方法返回值類型就可以是Element了。

如果我們需要將上面代碼在現(xiàn)有 Flutter 框架中跑起來,那么還是得提供一個“適配器”widget 將HomeView結(jié)合到現(xiàn)有框架中,下面CustomHome就相當于“適配器”:

class CustomHome extends Widget {
  @override
  Element createElement() {
    return HomeView(this);
  }
}

現(xiàn)在就可以將CustomHome添加到 widget 樹了,我們在一個新路由頁創(chuàng)建它,最終效果如下如圖14-1和14-2(點擊后)所示:

圖14-1 圖14-2

點擊按鈕則按鈕文本會隨機排序。

對于問題2,答案當然也是肯定的,F(xiàn)lutter engine 提供的 dart API 是原始且獨立的,這個與操作系統(tǒng)提供的 API 類似,上層 UI 框架設(shè)計成什么樣完全取決于設(shè)計者,完全可以將 UI 框架設(shè)計成 Android 風格或 iOS 風格,但這些事 Google 不會再去做,我們也沒必要再去搞這一套,這是因為響應(yīng)式的思想本身是很棒的,之所以提出這個問題,是因為筆者認為做與不做是一回事,但知道能不能做是另一回事,這能反映出我們對知識的理解程度。

#總結(jié)

本節(jié)詳細的介紹了Element的生命周期,以及它 Widget、BuildContext 的關(guān)系,也介紹了 Element 在 Flutter UI 系統(tǒng)中的角色和作用,我們將在下一節(jié)介紹 Flutter UI 系統(tǒng)中另一個重要的角色 RenderObject。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號