Flutter中有兩個常用的狀態(tài)Widget分為StatefulWidget和StatelessWidget,分別為動態(tài)視圖和靜態(tài)視圖,視圖的更新需要調(diào)用StatefulWidget的setState方法,這會遍歷調(diào)用子Widget的build方法。如果一個頁面內(nèi)容比較復(fù)雜時,會包含多個widget,如果直接調(diào)用setState,會遍歷所有子Widget的build,這樣會造成很多不必要的開銷,所以非常有必要了解Flutter中局部刷新的方式:
在源匯等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強發(fā)展的系統(tǒng)性、市場前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供網(wǎng)站制作、做網(wǎng)站 網(wǎng)站設(shè)計制作定制網(wǎng)站,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),品牌網(wǎng)站制作,營銷型網(wǎng)站建設(shè),外貿(mào)網(wǎng)站建設(shè),源匯網(wǎng)站建設(shè)費用合理。
globalkey唯一定義了某個element,它使你能夠訪問與element相關(guān)聯(lián)的其他對象,例如buildContext、state等。應(yīng)用場景:跨widget訪問狀態(tài)。
例如:可以通過key.currentState拿到它的狀態(tài)對象,然后就可以調(diào)用其中的onPressed方法。
Flutter框架內(nèi)部提供了一個非常小巧精致的組件,專門用于局部組件的刷新。適用于值改動的刷新。
實現(xiàn)原理:在 initState 中對傳入的可監(jiān)聽對象進行監(jiān)聽,執(zhí)行 _valueChanged 方法,_valueChanged 中進行了 setState 來觸發(fā)當前狀態(tài)的刷新。觸發(fā) build 方法,從而觸發(fā) widget.builder 回調(diào),這樣就實現(xiàn)了局部刷新??梢钥吹竭@里回調(diào)的 child 是組件傳入的 child,所以直接使用,這就是對 child 的優(yōu)化的根源。
可以看到 ValueListenableBuilder 實現(xiàn)局部刷新的本質(zhì),也是進行組件的抽離,讓組件狀態(tài)的改變框定在狀態(tài)內(nèi)部,并通過 builder 回調(diào)控制局部刷新,暴露給用戶使用。
通過這個可以創(chuàng)建一個支持局部刷新的widget樹,比如你可以在StatelessWidget里面刷新某個布局,但是不需要改變成StatefulWidget;也可以在StatefulWidget中使用做部分刷新而不需要刷新整個頁面,這個刷新是不會調(diào)用Widget build(BuildContext context)刷新整個布局樹的。
異步UI更新:
很多時候我們會依賴一些異步數(shù)據(jù)來動態(tài)更新UI,比如在打開一個頁面時我們需要先從互聯(lián)網(wǎng)上獲取數(shù)據(jù),在獲取數(shù)據(jù)的過程中顯示一個加載框,等獲取到數(shù)據(jù)時我們再渲染頁面;又比如我們想展示Stream(比如文件流、互聯(lián)網(wǎng)數(shù)據(jù)接收流)的進度。當然StatefulWidget我們完全可以實現(xiàn)以上功能。但由于在實際開發(fā)中依賴異步數(shù)據(jù)更新UI的這種場景非常常見,并且當StatefulWidget中控件樹較大時,更新一個屬性導(dǎo)致整個樹重建,消耗性能,因此Flutter專門提供了FutureBuilder和SteamBuilder兩個組件來快速實現(xiàn)這種功能。
通常情況下,子Widget無法單獨感知父Widget的變化,當父state變化時,通過其build重建所有子widget;
InheriteddWidget可以避免這種全局創(chuàng)建,實現(xiàn)局部子Widget更新。InheritedWidget提供了一種在Widget樹中從上到下傳遞、共享數(shù)據(jù)的方式。Flutter SDK正是通過InheritedWidget來共享應(yīng)用主題和Locale等信息。
InheritedWidgetData
TestData
InheritedTest1Page
provider是Google I/O 2019大會上宣布的現(xiàn)在官方推薦的管理方式,而ChangeNotifierProvider可以說是Provider的一種:
yaml文件需要引入provider: ^3.1.0
頂層嵌套ChangeNotifierProvider
創(chuàng)建共享數(shù)據(jù)類DataInfo:
數(shù)據(jù)類需要with ChangeNotifier 以使用 notifyListeners()函數(shù)通知監(jiān)聽者更新界面。
使用Provider.of(context)獲取DataInfo
nextPage:
使用Consumer包住需要使用共享數(shù)據(jù)的Widget
RepaintBoundary就是重繪邊界,用于重繪時獨立于父視圖。頁面需要更新的頁面結(jié)構(gòu)可以用 RepaintBoundary組件嵌套,flutter 會將包含的組件獨立出一層"畫布",去繪制。官方很多組件 外層也包了層 RepaintBoundary 標簽。如果你的自定義view比較復(fù)雜,應(yīng)該盡可能的避免重繪。
以上總結(jié)了幾種Flutter的局部刷新的方式,可根據(jù)實際需要使用不同的方式,最適合的才是最好的。
創(chuàng)建一個攔截器
重寫 onRequest 方法,在每次請求前header中加入 token
重寫 onError 方法,如果 errorCode 為 401 表示token過期,這時需要重新調(diào)用登錄接口獲取新的token,并將得到的新token寫入該次請求,重新發(fā)送這次請求,然后將創(chuàng)建好的攔截器加入到 dio 的攔截器數(shù)組中, _dio.interceptors.add()
setState會刷新整個頁面,在只需要刷新部分組件的時候可以用StatefulBuilder
先給需要局部刷新的組件創(chuàng)建一個StateSetter
需要刷新的組件用StatefulBuilder包裹,綁定_stateSetter
然后在需要刷新的時候調(diào)用
這種方法最常見,但是有些地方引用的話,刷新的成本比較大,刷新的是整個頁面,數(shù)據(jù)太多加載太慢的話,會有閃爍的現(xiàn)象
這種方法類似于iOS中的set方法,通過設(shè)置某個屬性的時候,去刷新某個控件。在flutter中這種刷新方式,是對上面setState(){}方法的改進,根本的方法還是setState(){},只不過是通過方法去刷新某個控件。如下:
首先在pubspec.yaml中添加provider依賴
下面通過provider來實現(xiàn)一個發(fā)送驗證碼的案例。
創(chuàng)建一個TimerModel文件
頁面布局如下:
Flutter有兩個常用的狀態(tài)類:
標記為dirty,執(zhí)行的markNeedsBuild,定義在Element類中:
當前Element節(jié)點被標記為dirty,同時調(diào)用owner的scheduleBuildFor方法:
將element元素添加到全局的“臟”鏈表里。
BuildOwner用來管理哪些需要更新的Widget。這個owner最開始被初始化的地方在WidgetsBinding的initInstances方法中,隨后初始化了onBuildScheduled方法,對應(yīng)執(zhí)行的是_handleBuildScheduled,定義在WidgetsBinding類中:
ensureVisualUpdate 方法定義在SchedulerBinding類中:
在提交下一幀繪制的時候會調(diào)用到scheduleFrame方法,提交給引擎繪制,看看scheduleFrame方法,也定義在SchedulerBinding類中:
提交給引擎繪制之后,會收到onDrawFrame的回調(diào),最終執(zhí)行到_handleDrawFrame方法中,對應(yīng)的是handleDrawFrame方法,定義在SchedulerBinding類中:
在RendererBinding的initInstances方法中添加了一個回調(diào)到這個List中,對應(yīng)的是RenderBinding的drawFrame方法,對應(yīng)的節(jié)點進行繪制渲染操作。
WidgetsBinding中的drawFrame方法:
看看這里的buildScope方法,定義在BuildOwner方法中。在上面 scheduleBuildFor 方法介紹中有提到:"scheduleBuildFor 是把一個 element 添加到 _dirtyElements 鏈表,以便當[WidgetsBinding.drawFrame]中調(diào)用 buildScope 的時候能夠重構(gòu) element。onBuildScheduled()是一個 BuildOwner 的回調(diào)"。在 drawFrame 中調(diào)用 buildOwner.buildScope(renderViewElement)更新 elements。
_dirtyElements列表在遍歷的過程中執(zhí)行rebuild方法,此時將所有標記為dirty的Element節(jié)點依次執(zhí)行rebuild,preformRebuild,build,updateChild,update方法,執(zhí)行界面更新。完成build,update操作完成之后,后續(xù)會將需要繪制的RenderObject添加到需要layout的列表中,等待繪制渲染。所有繪制完成之后將_dirtyElments列表清空,_inDirtyList標記位置為false。
提交給引擎繪制渲染
看看super.drawFrame(),這里就執(zhí)行到了RendererBinding類中,定義如下:
這里就是將最終需要繪制渲染的畫面提交給引擎的地方了,繪制完成之后就在界面顯示更新后的視圖了。
Flutter中Widget分為StatefulWidget和StatelessWidget,分別為動態(tài)視圖和靜態(tài)視圖,視圖的更新需要調(diào)用StatefulWidget的setState方法,這會遍歷調(diào)用子Widget的build方法。當一個主頁面比較復(fù)雜時,會包含多個widget,如果直接調(diào)用setState,會遍歷所有子Widget的build,這是非常不必要的性能開銷,有沒有單獨刷新指定Widget的方式呢?這個時候就要用到GlobalKey了。
一個StatefulWidget包含一個Button,一個Text,通過點擊Button調(diào)用主Widget的setState方法,刷新Text,示例如下:
同樣一個StatefulWidget包含一個多個Text和Button,點擊Button我們只需要刷新指定的Text,通過GlobalKey的方式,實現(xiàn)如下:
主Widget,包含一個需要更新的TextWidget和一個不需要更新的Text
需要單獨更新的Widget
傳遞事件的Button
這樣點擊Button就只會更新指定的TextWidget了,效果如下:
這只是一個簡單的例子,在實際開發(fā)中為了頁面刷新的高效率,模塊化封裝非常重要。很多情況下都只需要局部刷新,而不是重構(gòu)整個視圖。所以Globalkey的運用在項目中需要熟練掌握