-
概述
在Flutter中,我們知道,刷新界面要調(diào)用setState方法,在一個(gè)界面中,通常只需要刷新某個(gè)組件或者某一部分組件,這種情況下調(diào)用父級(jí)State的setState方法會(huì)造成不必要的資源浪費(fèi)。 在這種需求下,我們需要找到一個(gè)方式可以進(jìn)行局部刷新。
-
做法和原理
其實(shí)局部刷新很簡(jiǎn)單,我們只需要把需要刷新的組件聚到一個(gè)StatefulWidget中,通過一個(gè)State來管理,然后刷新的時(shí)候調(diào)用這個(gè)State的setState方法即可完成針對(duì)這部分組件的刷新,父級(jí)和兄弟級(jí)的StatefulWidget都不會(huì)被引起rebuild。
原理也很好理解,就是調(diào)用setState的流程,調(diào)用setState方法:
@protected void setState(VoidCallback fn) { ... _element!.markNeedsBuild(); }Element的markNeedsBuild方法會(huì)調(diào)用BuildOwner的scheduleBuildFor方法:
void markNeedsBuild() { ... if (dirty) return; _dirty = true; owner!.scheduleBuildFor(this); }scheduleBuildFor方法會(huì)把當(dāng)前element放入BuildOwner的_dirtyElements中:
void scheduleBuildFor(Element element) { ... _dirtyElements.add(element); element._inDirtyList = true; ... }當(dāng)下一個(gè)Frame到來時(shí)框架會(huì)調(diào)用WidgetsBinding的drawFrame方法:
@override void drawFrame() { ... try { if (renderViewElement != null) buildOwner!.buildScope(renderViewElement!); //這里面是布局、合成層信息、繪制等流程 super.drawFrame(); buildOwner!.finalizeTree(); } finally { ... } ... }這里會(huì)調(diào)用BuildOwner的buildScope方法:
@pragma('vm:notify-debugger-on-exception') void buildScope(Element context, [ VoidCallback? callback ]) { ... try { ... _dirtyElements.sort(Element._sort); ... int dirtyCount = _dirtyElements.length; int index = 0; while (index < dirtyCount) { ... try { //重新構(gòu)建 _dirtyElements[index].rebuild(); } catch (e, stack) { ... } index += 1; ... } ... } finally { for (final Element element in _dirtyElements) { assert(element._inDirtyList); element._inDirtyList = false; } //清空_dirtyElements _dirtyElements.clear(); ... } ... }在buildScope方法中會(huì)循環(huán) _dirtyElements,依次調(diào)用里面的element的rebuild方法進(jìn)行構(gòu)建,rebuild方法中又會(huì)調(diào)用performRebuild方法:
@pragma('vm:prefer-inline') void rebuild() { ... performRebuild(); ... }performRebuild方法是在StatefulElement和StatelessElement的共同父類ComponentElement中實(shí)現(xiàn)的,在這個(gè)方法中會(huì)調(diào)用build方法創(chuàng)建Widget:
//StatefulElement中實(shí)現(xiàn)的build方法,可見會(huì)通過state的build方法生成 @override Widget build() => state.build(this); //StatelessElement中實(shí)現(xiàn)的build方法 @override Widget build() => widget.build(this);所以局部刷新原理的核心就是把需要刷新的區(qū)域收到一個(gè)State中,然后調(diào)用這個(gè)State的setState方法就會(huì)使當(dāng)前的這個(gè)State的element變?yōu)閐irty,把它放入需要重新構(gòu)建的element集合中,在幀回調(diào)后會(huì)循環(huán)這個(gè)集合調(diào)用它的rebuild方法進(jìn)行重新構(gòu)建,因?yàn)槲覀兏弦患?jí)的State并沒有執(zhí)行它的setState方法所以不會(huì)添加在需要重新構(gòu)建的element集合中。
-
關(guān)于get框架的應(yīng)用
get框架的局部刷新也是通過上面的原理完成的,下面我們來看看他是怎么封裝的。
首先它使用一個(gè)叫做GetxController的東西來提供統(tǒng)一刷新的api接口:
abstract class GetxController extends DisposableInterface with ListenableMixin, ListNotifierMixin { void update([List<Object>? ids, bool condition = true]) { if (!condition) { return; } //全部刷新 if (ids == null) { refresh(); } else { //局部刷新 for (final id in ids) { refreshGroup(id); } } } }在頁面打開的時(shí)候會(huì)創(chuàng)建這個(gè)controller,然后通過調(diào)用這個(gè)controller的update方法執(zhí)行局部構(gòu)建,可以看到,局部構(gòu)建需要一個(gè)id,這個(gè)id是什么時(shí)候綁定的呢?
使用get框架的局部刷新需要把要刷新的組件們用一個(gè)GetBuilder包裝起來,那這個(gè)GetBuilder構(gòu)造時(shí)就可以傳入一個(gè)id值,GetBuilder是一個(gè)StatefulWidget,他的State中的initState方法里調(diào)用了一個(gè)_subscribeToController方法:
void _subscribeToController() { _remove?.call(); _remove = (widget.id == null) //全部刷新的回調(diào)添加 ? controller?.addListener( _filter != null ? _filterUpdate : getUpdate, ) //局部刷新的回調(diào)添加 : controller?.addListenerId( widget.id, _filter != null ? _filterUpdate : getUpdate, ); }addListenerId方法中:
Disposer addListenerId(Object? key, GetStateUpdate listener) { _updatersGroupIds![key] ??= <GetStateUpdate>[]; _updatersGroupIds![key]!.add(listener); return () => _updatersGroupIds![key]!.remove(listener); }可以看到,這里根據(jù)id添加了一個(gè)回調(diào)函數(shù),這里用的數(shù)組存放,可見可以通過指定同一個(gè)id的方式來實(shí)現(xiàn)幾個(gè)區(qū)域聯(lián)動(dòng)刷新。
回到上面的refreshGroup方法,內(nèi)部會(huì)調(diào)用_notifyIdUpdate方法:
void _notifyIdUpdate(Object id) { if (_updatersGroupIds!.containsKey(id)) { final listGroup = _updatersGroupIds![id]!; for (var item in listGroup) { item(); } } }可見,在這里根據(jù)id查找并執(zhí)行了所有相關(guān)的函數(shù)回調(diào)。
那么函數(shù)回調(diào)是什么呢?_subscribeToController方法中,addListenerId方法添加的函數(shù)回調(diào)如果默認(rèn)的話是getUpdate,它指向一個(gè)函數(shù),這個(gè)函數(shù)在GetBuilderState依賴的mixin—GetStateUpdaterMixin中定義:
void getUpdate() { if (mounted) setState(() {}); }可以看到,正是在這里調(diào)用了setState來觸發(fā)重新構(gòu)建的,因?yàn)槭窃贕etBuilderState中調(diào)用的setState方法,所以在GetBuilder之上的其他State是不會(huì)觸發(fā)回調(diào)的,這和上面我們分析的原理是一樣的。
-
總結(jié)
局部構(gòu)建的原理就是用子State來攔截構(gòu)建的范圍,不把所有的組件樹都放在一個(gè)大的State里面構(gòu)建,通過調(diào)用子State的setState方法來實(shí)現(xiàn)針對(duì)子Widget樹的重新構(gòu)建,這樣就實(shí)現(xiàn)了局部刷新。
據(jù)此,我們當(dāng)然可以不用get框架的局部刷新,完全可以自定義,我試著寫了一下,有幾個(gè)需要注意的點(diǎn):
setState一定要在需要局部刷新的State中調(diào)用;
調(diào)用setState的邏輯要通過一個(gè)函數(shù)暴露出來;
因?yàn)槲覀円WC隨時(shí)可以刷新,所以我們需要一個(gè)隨時(shí)獲取且不會(huì)改變的對(duì)象來保存這個(gè)回調(diào),相當(dāng)于get的controller;
-
因?yàn)槲覀兛赡軙?huì)有很多個(gè)局部需要刷新,它們必須獨(dú)立且可以區(qū)分,所以我們需要保存回調(diào)函數(shù)的集合是一個(gè)可已按照key-value的形式來存放的集合,get中使用了String-List的形式,這樣可以刷新好幾塊區(qū)域,我用的是一個(gè)String-dynamic的Map來存放,這個(gè)過程中發(fā)現(xiàn)了一個(gè)需要注意的點(diǎn):
Map的putIfAbsent方法的第二個(gè)參數(shù)規(guī)定是:
V putIfAbsent(K key, V ifAbsent());如果使用這個(gè)方法設(shè)置回調(diào)函數(shù),則需要在一個(gè)函數(shù)中返回這個(gè)回調(diào)函數(shù)才行。
Flutter局部刷新原理
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
相關(guān)閱讀更多精彩內(nèi)容
- Flutter中有兩個(gè)常用的狀態(tài)Widget分為StatefulWidget和StatelessWidget,分別...
- 拋磚引玉,Element的刷新機(jī)制 我們知道flutter的整個(gè)視圖層是一個(gè)樹狀結(jié)構(gòu),以父子節(jié)點(diǎn)的形式進(jìn)行布局繪制...
- Flutter在內(nèi)部實(shí)際上是如何工作的? 什么是Widget、 Element、 BuildContext、 Re...
- Flutter狀態(tài)類 Flutter開發(fā)當(dāng)中總共有兩種狀態(tài)的Widget,一種是StatelessWidget;另...
- 哈羅大家好,這個(gè)是我們Flutter的原理篇第二篇內(nèi)容,第一篇的內(nèi)容大家感興趣的話可以點(diǎn)擊這個(gè)《Flutter原理...