<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # Element與BuildContext ### Element 在“Widget簡介”一節,我們介紹了Widget和Element的關系,我們知道最終的UI樹其實是由一個個獨立的Element節點構成。我們也知道了組件最終的Layout、渲染都是通過RenderObject來完成的,從創建到渲染的大體流程是:根據Widget生成Element,然后創建相應的RenderObject并關聯到Element.renderObject屬性上,最后再通過RenderObject來完成布局排列和繪制。 Element就是Widget在UI樹具體位置的一個實例化對象,大多數Element只有唯一的renderObject,但還有一些Element會有多個子節點,如繼承自RenderObjectElement的一些類,比如MultiChildRenderObjectElement。最終所有Element的RenderObject構成一棵樹,我們稱之為渲染樹,即render tree。 Element的生命周期如下: 1. Framework 調用`Widget.createElement` 創建一個Element實例,記為`element` 2. Framework 調用 `element.mount(parentElement,newSlot)` ,mount方法中首先調用`elment`所對應Widget的`createRenderObject`方法創建與`element`相關聯的RenderObject對象,然后調用`element.attachRenderObject`方法將`element.renderObject`添加到渲染樹中插槽指定的位置(這一步不是必須的,一般發生在Element樹結構發生變化時才需要重新attach)。插入到渲染樹后的`element`就處于“active”狀態,處于“active”狀態后就可以顯示在屏幕上了(可以隱藏)。 3. 當`element`父Widget的配置數據改變時,為了進行Element復用,Framework在決定重新創建Element前會先嘗試復用相同位置舊的element:調用Element對應Widget的`canUpdate()`方法,如果返回`true`,則復用舊Element,舊的Element會使用新的Widget配置數據更新,反之則會創建一個新的Element,不會復用。`Widget.canUpdate()`主要是判斷`newWidget`與`oldWidget`的`runtimeType`和`key`是否同時相等,如果同時相等就返回`true`,否則就會返回`false`。根據這個原理,當我們需要強制更新一個Widget時,可以通過指定不同的Key來禁止復用。 4. 當有父Widget的配置數據改變時,同時其`State.build`返回的Widget結構與之前不同,此時就需要重新構建對應的Element樹。為了進行Element復用,在Element重新構建前會先嘗試是否可以復用舊樹上相同位置的element,element節點在更新前都會調用其對應Widget的`canUpdate`方法,如果返回`true`,則復用舊Element,舊的Element會使用新Widget配置數據更新,反之則會創建一個新的Element。`Widget.canUpdate`主要是判斷`newWidget`與`oldWidget`的`runtimeType`和`key`是否同時相等,如果同時相等就返回`true`,否則就會返回`false`。根據這個原理,當我們需要強制更新一個Widget時,可以通過指定不同的Key來避免復用。 5. 當有祖先Element決定要移除`element` 時(如Widget樹結構發生了變化,導致`element`對應的Widget被移除),這時該祖先Element就會調用`deactivateChild` 方法來移除它,移除后`element.renderObject`也會被從渲染樹中移除,然后Framework會調用`element.deactivate` 方法,這時`element`狀態變為“inactive”狀態。 6. “inactive”態的element將不會再顯示到屏幕。為了避免在一次動畫執行過程中反復創建、移除某個特定element,“inactive”態的element在當前動畫最后一幀結束前都會保留,如果在動畫執行結束后它還未能重新變成”active“狀態,Framework就會調用其`unmount`方法將其徹底移除,這時element的狀態為`defunct`,它將永遠不會再被插入到樹中。 7. 如果`element`要重新插入到Element樹的其它位置,如`element`或`element`的祖先擁有一個GlobalKey(用于全局復用元素),那么Framework會先將element從現有位置移除,然后再調用其`activate`方法,并將其`renderObject`重新attach到渲染樹。 看完Element的生命周期,可能有些讀者會有疑問,開發者會直接操作Element樹嗎?其實對于開發者來說,大多數情況下只需要關注Widget樹就行,Flutter框架已經將對Widget樹的操作映射到了Element樹上,這可以極大的降低復雜度,提高開發效率。但是了解Element對理解整個Flutter UI框架是至關重要的,Flutter正是通過Element這個紐帶將Widget和RenderObject關聯起來,了解Element層不僅會幫助讀者對Flutter UI框架有個清晰的認識,而且也會提高自己的抽象能力和設計能力。另外在有些時候,我們必須得直接使用Element對象來完成一些操作,比如獲取主題Theme數據,具體細節將在下文介紹。 ### BuildContext 無論是StatelessWidget和StatefulWidget的build方法都會傳一個BuildContext對象: ``` Widget build(BuildContext context) {} ``` 我們知道,在很多時候我們都需要使用這個`context` 做一些事,比如: ``` Theme.of(context) //獲取主題 Navigator.push(context, route) //入棧新路由 Localizations.of(context, type) //獲取Local context.size //獲取上下文大小 context.findRenderObject() //查找當前或最近的一個祖先RenderObject ``` 那么BuildContext到底是什么呢,查看其定義,發現其是一個抽象接口類: ``` abstract class BuildContext { ... } ``` 那StatelessWidget和StatefulWidget的build方法傳入的context對象是哪個實現了BuildContext的類。我們順藤摸瓜,發現調用時發生在StatelessWidget和StatefulWidget對應的StatelessElement和StatefulElement的build方法中,以StatelessElement為例: ``` class StatelessElement extends ComponentElement { ... @override Widget build() => widget.build(this); ... } ``` 發現build傳遞的是this,很明顯了,這個BuildContext很可能就是Element類,查看Element類定義,發現Element類果然實現了BuildContext接口: ``` class Element extends DiagnosticableTree implements BuildContext { ... } ``` 至此真相大白,BuildContext就是Widget對應的Element,所以我們可以通過context在StatelessWidget和StatefulWidget的build方法中直接訪問Element對象。我們獲取主題數據的代碼`Theme.of(context)`內部正是調用了Element的`inheritFromWidgetOfExactType()`方法。 > 思考題:為什么build方法的參數不定義成Element對象,而要定義成BuildContext ? ### 進階 我們可以看到Element是Flutter UI框架內部連接Widget和RenderObject的紐帶,大多數時候開發者只需要關注Widget層即可,但是Widget層有時候并不能完全屏蔽Element細節,所以Framework在StatelessWidget和StatefulWidget中通過build方法參數將Element對象也傳遞給了開發者,這樣便可以在需要時直接操作Element對象。那么現在筆者提兩個問題,請讀者先自己思考一下: 1. 如果沒有Widget層,單靠Element層是否可以搭建起一個可用的UI框架?如果可以應該是什么樣子? 2. Flutter UI框架能不做成響應式嗎? 對于問題1,答案當然是肯定的,因為我們之前說過Widget樹只是Element樹的映射,我們完全可以直接通過Element來搭建一個UI框架。下面舉一個例子: 我們通過純粹的Element來模擬一個StatefulWidget的功能,假設有一個頁面,該頁面有一個按鈕,按鈕的文本是1-9 9個數,點擊一次按鈕,則對9個數隨機排一次序,代碼如下: ``` 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方法不接收參數,這一點和在StatelessWidget和StatefulWidget中build(BuildContext)方法不同。代碼中需要用到BuildContext的地方直接用`this`代替即可,如代碼注釋1處`Theme.of(this)`參數直接傳`this`即可,因為當前對象本身就是Element實例。 - 當`text`發生改變時,我們調用`markNeedsBuild()`方法將當前Element標記為dirty即可,標記為dirty的Element會在下一幀中重建。實際上,`State.setState()`在內部也是調用的`markNeedsBuild()`方法。 - 上面代碼中build方法返回的仍然是一個Widget,這是由于Flutter框架中已經有了Widget這一層,并且組件庫都已經是以Widget的形式提供了,如果在Flutter框架中所有組件都像示例的HomeView一樣以Element形式提供,那么就可以用純Element來構建UI了,HomeView的build方法返回值類型就可以是Element了。 如果我們需要將上面代碼在現有Flutter框架中跑起來,那么還是得提供一個”適配器“Widget將HomeView結合到現有框架中,下面CustomHome就相當于”適配器“: ``` class CustomHome extends Widget { @override Element createElement() { return HomeView(this); } } ``` 現在就可以將CustomHome添加到Widget樹了,我們在一個新路由頁創建它,最終效果如下: ![](https://box.kancloud.cn/075fa468bf638f40bf6ce70720d00eea_320x569.png)![](https://box.kancloud.cn/7141498f3624a2cd6c4a5f80cdf7a661_320x569.png) 點擊按鈕則按鈕文本會隨機排序。 對于問題2,答案當然也是肯定的,Flutter engine提供的dart API是原始且獨立的,這個與操作系統提供的API類似,上層UI框架設計成什么樣完全取決于設計者,完全可以將UI框架設計成Android風格或iOS風格,但這些事Google不會再去做,我們也沒必要再去搞這一套,這是因為響應式的思想本身是很棒的,之所以提出這個問題,是因為筆者認為做與不做是一回事,但知道能不能做是另一回事,這能反映出我們對知識的掌握程度。 ### 總結 本節詳細的介紹了Element的生命周期,以及它與Widget、BuildContext的關系,也介紹了Element在Flutter UI系統中的角色和作用,我們將在下一節介紹Flutter UI系統中另一個重要的角色RenderObject。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看