亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

ProxyWidget和Element更新的正確方式詳解

 更新時間:2023年01月18日 09:51:28   作者:開中斷  
這篇文章主要為大家介紹了ProxyWidget和Element更新的正確方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

正文

Flutter的眾多Widget當中,有作用于渲染的RenderObjectWidget、聚焦于功能整合的StatefulWidget。但是,還有一個大類,ProxyWidget也同樣值得我們關(guān)注。

與其相關(guān)的有兩個大類:

  • InhertedWidget
  • ParentDataWidget(代表:Positioned、Expanded)

這兩個Widget,無非都是數(shù)據(jù)的向下傳遞,其一InheritedWidget更多的是業(yè)務數(shù)據(jù),比如用戶的ID、購物車的條目等等,而ParentDataWidget一般都是視圖的數(shù)據(jù),Stack需要使用parentData參數(shù)中的長寬、偏移量來完成對子Widget的定位。

所以,我們可以根據(jù)ProxyWidget的子類,向上預先給ProxyWidget扣一個數(shù)據(jù)共享的「帽子」。

1. ProxyWidget和ProxyElement的主要功能

ProxyWidget本身是抽象的,需要我們重寫它的createElement()方法:

class CustomProxyWidget extends ProxyWidget {
  const CustomProxyWidget({required Widget child}) : super(child: child);
  @override
  Element createElement() => CustomProxyElement(this);
}

而ProxyElement則要重寫notifyClients方法。

class CustomProxyElement extends ProxyElement {
  CustomProxyElement(ProxyWidget widget) : super(widget);
  @override
  void notifyClients(covariant ProxyWidget oldWidget) {
    //......
  }
}

整個ProxyElement的關(guān)鍵代碼,就notifyClients這個函數(shù)的實現(xiàn),它傳入了一個老的、支持協(xié)變的ProxyWidget進來,這意味著傳進來的應該是一個老的CustomProxyWidget的實例,這意味著我們在notifyClients中,可以同時拿到老的CustomProxyWidget實例和當前CustomProxyWidget實例的引用,分別是oldWidgetthis.widget。

一新一舊,不難看出ProxyWidget的notifyClients調(diào)用,應該是要去做一些新舊Widget的數(shù)據(jù)比較而存在的。

比如,我們可以這樣重寫它:

@override
void notifyClients(covariant ProxyWidget oldWidget) {
  if((oldWidget as CustomProxyWidget).data != (widget as CustomProxyWidget).data){
    // 通知所有訂閱者,數(shù)據(jù)變動了
    _clients.foreach((e)=>e.notify());
  }
}

我們可以根據(jù)data屬性(data是CustomProxyWidget新增的一個int類型的字段)的變化,來決定是否需要通知訂閱者的Element是否去重新繪制子Widget,一旦data發(fā)生了變化,那么就去遍歷_clients中的數(shù)據(jù),并調(diào)用e.notify操作監(jiān)聽者重新繪制視圖。

這讓我們不禁和InheritedWidgetupdateShouldNotify聯(lián)系起來,簡單分析一下updateShouldNotify的調(diào)用鏈條:

InhertiedElement#update -> updateShouldNotify() 判斷是否需要更新數(shù)據(jù)
InhertiedElement#update -> callsuper 即調(diào)用ProxyElement的update方法
ProxyElement#update -> notifyClients();

顯然,InheritedWidget將notifyClients做了一個封裝updateShouldNotify,并把這個封裝放在Widget層,而不是直接讓開發(fā)者去重寫notifyClients這一層,這么做的原因其實和BuildContext存在的意義是一樣的,讓上層應用開發(fā)者只關(guān)注Widget,而更少地去感知Element的存在。

總而言之,notifyClients存在的作用和意義,就是通知訂閱它的子Widget,以實現(xiàn)子Widget的更新,我們也能稍稍瞥見一些ProxyWidget和ProxyElement的作用,大體上都是和數(shù)據(jù)傳輸和共享相關(guān)的。

2. InheritedWidget

基于觀察者模式的InheritedWidget,它的使用我們就不做過多的敘述了,整體上而言,就三步走:

  • 注冊:利用BuildContext注冊監(jiān)聽
  • 通過BuildContext獲取數(shù)據(jù)
  • 通知:改變促進監(jiān)聽者的數(shù)據(jù)重繪

這是一個非常典型的觀察者模式的使用步驟,只不過InheritedWidget為我們做了一些封裝,「注冊」、「通知」操作變得更加地“隱蔽”了。

2.1 注冊

使用InheritedWidget時,我們并沒有手動地調(diào)用addListener、addObserver這類的方法,去主動添加監(jiān)聽,這一切都是無感的。我們一般通過如下方法獲取到InheritedWidget中的數(shù)據(jù)。

context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();

這一行代碼已經(jīng)包括兩個步驟了:注冊監(jiān)聽和獲取數(shù)據(jù)。

InheritedElement當中,有一個特殊的結(jié)構(gòu),它存儲了我們上面通過context調(diào)用時的context,這樣來實現(xiàn)注冊的監(jiān)聽,并且,在注冊完成之后,會將所需要的數(shù)據(jù)返回給調(diào)用者,這樣一來,監(jiān)聽注冊、數(shù)據(jù)的獲取這一個操作就合二為一了。

final Map<Element, Object?> _dependents = HashMap<Element, Object?>();

2.2 通知

對于StatefulWidget的重繪,我們一定會想到一個方法:markNeedsBuild(),所以,我們就順著上述的調(diào)用,查找是否有相關(guān)的調(diào)用,我們可以看看屬于InheritedElement的notifyClients的調(diào)用鏈:

 InheritedElement# notifyClients
 InheritedElement# notifyDependent(oldWidget, dependent);
 dependent#didChangeDependencies();

一路從notifyClients調(diào)用到_dependents中的某個dependentdidChangeDependencies方法,這就是通知的整個流程,InheritedWidget通過這樣的調(diào)用,通知所有掛載著的監(jiān)聽者,即其他需要InheritedWidget數(shù)據(jù)的Widget的BuildContext,并調(diào)用BuildContext的didChangeDependencies,它的實現(xiàn)如下:

@mustCallSuper
void didChangeDependencies() {
  ……
  markNeedsBuild();
}

至此,InheritedWidget是如何通知到子Widget進行更新的整個鏈路已經(jīng)是非常清晰了。

由于didChangedDepenedencies()的存在,只有添加了依賴的結(jié)點才會因為數(shù)據(jù)的更新而造成節(jié)點的rebuild,而不會像StatefulWidget一樣,對整棵子樹做一次完全的rebuild,這是整個ProxyWidget/ProxyElement的特性。

2.3 何時更新?

InheritedWidget自身只負責數(shù)據(jù)的向下傳遞,子Widget可以從InheritedWidget中讀出數(shù)據(jù),但是,諸如我們的子Widget中的onPressed的回調(diào)函數(shù)中,對InheritedWidget中的數(shù)據(jù)進行修改,通常情況下是無法實現(xiàn)UI的更新的,因為InheritedWidget調(diào)用notifyClients()是有時機限制的。

僅當是ProxyElement#update()被調(diào)用時,才會調(diào)用updateShouldNotify()去評估是否要調(diào)用notifyClients去更新布局。而一般都數(shù)據(jù)修改,例如int++String賦值等等并不能觸發(fā)notifyClients調(diào)用。

所以,只有Element#update()方法調(diào)用時,才能驅(qū)動子Widget發(fā)生視圖更新,而Element#update()方法僅在:Element不變,Widget發(fā)生改變的時候才會觸發(fā),常見于Widget作為一個配置,發(fā)生了改變,而Element發(fā)生了復用的情況。比如State調(diào)用build方法構(gòu)建了一個新的Widget子樹,這個子樹中的Widget都是全新的Widget,并且如果只是修改Text對應的String中的內(nèi)容,Text對應的Element此時就會發(fā)生復用,這個過程就是Element的update(),即 用新的newWidget替換掉舊的oldWidget的過程,可以理解為Element的配置的改變。

所以,InheritedWidget的更新就必須依賴于InheritedWidget的上層更新,比如去調(diào)用setState等等,這個觸發(fā)條件似乎有一點苛刻了,我們肯定是希望在子Widget中修改了InheritedWidget中的數(shù)據(jù)之后,就直接就能反應到視圖。

我們可以在onPressed等回調(diào)方法中,調(diào)用完修改方法之后,手動調(diào)用一下setState來手動重建Widget,也可以在InheritedWidget中自己定義一個相關(guān)的方法,傳入Context,統(tǒng)一處理。

3. ParentDataWidget

之前介紹InheritedWidget主要是講了它作為ProxyWidget,它的notifyClients是如何實現(xiàn)的,作為ProxyWidget的另一個分支,ParentDataWidget也是一個非常常用的Widget,它的常見實現(xiàn)類包括:Flexible(常用Expanded)、Positioned等等。它們都有一個非常明顯的特點:具有一個其父組件(Flext、Stack)需要的一個額外信息,父組件會使用這個額外的信息對當前組件進行布局、定位。

相比較于InheritedWidget,ParentDataWidget的使用場景更多的是偏向于視圖本身的數(shù)據(jù),比如尺寸、偏移量等等。

3.1 Positioned

首先我們來看看Positioned,Stack嵌套Positioned,在Positioned可以設置height/width和left/top/right/bottom等一系列的尺寸、位置屬性,我們需要關(guān)注的,是ParentDataElement對應的的notifyClients究竟干了些什么。

我們先來看看Positioned的功能。Positioned先將傳遞進來的renderObject對象中的parentData結(jié)構(gòu)取出,然后再向其中塞數(shù)據(jù),之后的布局過程中,Stack就可以根據(jù)StackParentData中的數(shù)據(jù)進行布局了。

ParentDataElement的notifyClients方法,只調(diào)用了一個方法,我們可以快速地定位到_applyParentData方法:

@override
void applyParentData(RenderObject renderObject) {
  assert(renderObject.parentData is StackParentData);
  final StackParentData parentData = renderObject.parentData! as StackParentData;
  ……
}

這里傳進來的正是Positioned的child屬性對應的RenderObject,Positioned將設置的尺寸、偏移量作為一個StackParentData傳遞進去,然后再Render階段對其進行位置的確定和布局。

接下來的場景如下:Stack下面套了三個Positioned,對應三個具有顏色的Container。

Positioned本身是不參與Render的,我們可以很清楚地看到,RenderStack的child直接就是RenderColoredBox,即一個具有顏色的Box,是由Container創(chuàng)建的,而不是一個Positioned(Container本身是一個復合型的StatelessWidget)。我們可以模糊地理解成,RenderTree下,Stack下直接就是Container。

ProxyWidget還是會存在于Element、Widget樹當中的,只是在渲染的時候,它并不是一個RenderObject節(jié)點,所以,自然而然不參與渲染,但是它的數(shù)據(jù)還存在它的孩子對應的ParentData當中。重新構(gòu)建時,也是調(diào)用renderObject.parent(在RenderTree上的parent,即Stack)進行重建

所以,ProxyWidget本身是不參與渲染的,他只作為一個中間Widget,為下層的Child對應的RenderObject,提供上層(Stack)所需要的數(shù)據(jù)(尺寸、偏移量等等)。

同為ParentDataWidget的Flexible同理,只不過把適用于Stack的StackParentData,換成了適用于Flex的FlexParentData,以StackParentData為例,我們只需要知道它的數(shù)據(jù)是記錄在Postioned的child對應的RenderObject下,交給的父布局Stack使用即可,ParentDataWidget的使命也僅限于此。

4. 后記

既然Positioned對應的Element也是ProxyElement的子類,那么它的notifyClients的調(diào)用就和InheritedWidget相同,當Element#update調(diào)用時,才會調(diào)用notifyClients,去重新為子Widget設置StackParentData(尺寸、寬高數(shù)據(jù)),然后去重新布局子Widget。

這也是ProxyElement一貫的處理方式,當ProxyWidget對應的數(shù)據(jù)發(fā)生改變(InheritedWidget一般是業(yè)務數(shù)據(jù),ParentDataWidget一般是一些視圖數(shù)據(jù)),才會去重建視圖,而Widget數(shù)據(jù)發(fā)生改變的唯一方法,就是重新創(chuàng)建一個Widget,而不是在原有的Widget上通過回調(diào)等手段來進行賦值、增減等等,這種情況并不視為Widget的改變。

從Element的角度來說,如果Widget想要改變就必然要通過Element#update方法,即使是StatefulWidget,它的改變也是從State調(diào)用setState開始,然后StatefulWidget去rebuild一個新的Child Widget子樹,再調(diào)用Element的update方法,將新的子樹掛載上來完成新舊數(shù)據(jù)的更迭。

簡單來說,默認情況下,數(shù)據(jù)的變更必須精確到Widget層面,Element才有可能看得見。

一旦認為數(shù)據(jù)發(fā)生了改變,那么ProxyElement則會通過notifyClients方法,通知所有的監(jiān)聽者,監(jiān)聽者此時的行為:

  • 如果是InheritedWidget,那么就是調(diào)用監(jiān)聽者的didChangeDependencies,重建監(jiān)聽者對應的視圖。
  • 如果是ParentDataWidget,那么就是調(diào)用ParentDataElement的applyParentData函數(shù),去重新build它的子集。

以上就是ProxyWidget和Element更新的正確方式詳解的詳細內(nèi)容,更多關(guān)于ProxyWidget Element更新的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論