-
概述
Flutter中提供了對話框組件和彈出對話框的方法,但是這些方法都需要傳入一個BuildContext參數(shù),這使得我們必須要在構(gòu)建樹中傳入BuildContext才行,這限制了我們調(diào)起彈框的位置,所以我們需要一個可以在任何位置顯示和隱藏的彈框。
-
原理
Flutter的原生調(diào)用中,提供了諸如AlertDialog、SimpleDialog、Dialog等組件,并且提供了諸如showDialog、showModalBottomSheet(底部彈窗)等方法來顯示Dialog。
組件部分沒什么好說的,我們可以定義自己的彈框樣式,主要是顯示和隱藏的原理。
我們來想想一下可以實現(xiàn)一個對話框效果的方法。
我們知道,F(xiàn)lutter的組件是通過構(gòu)建組件樹的方式顯示在界面上的,如果要實現(xiàn)一個對話框的效果,我們可能會首先想到通過Stack來實現(xiàn),把對話框放在Statck的最后一個,然后通過它的顯示和隱藏來達到效果。但是這樣一來,我們的對話框就和組件樹綁定在一起了,在實現(xiàn)層面上,它不會影響最終的呈現(xiàn),但是在規(guī)范上總覺得它不該屬于組件樹。不僅如此,這樣實現(xiàn),我們每個頁面需要對話框的時候都需要和頁面組件樹綁定,重復代碼太多,每個頁面都需要維護一個自己的彈框。所以,這種方式可以實現(xiàn)但是太不靈活。
那原生是怎么做的?Flutter中使用路由的方式來實現(xiàn)對話框。
showDialog方法中:
return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(DialogRoute<T>( context: context, builder: builder, barrierColor: barrierColor, barrierDismissible: barrierDismissible, barrierLabel: barrierLabel, useSafeArea: useSafeArea, settings: routeSettings, themes: themes, ));可以看到,顯示就是一個push一個新頁面,那隱藏很明顯就是調(diào)用Navigator的pop方法。
DialogRoute會間接繼承自PopupRoute,這個類中重寫了ModalRoute的兩個屬性值:
//這個屬性可以設(shè)置遮蓋層透明度是否完全遮蓋住前一個頁面 @override bool get opaque => false; //這個屬性設(shè)置當該route不可見時是否可以被內(nèi)存殺死 @override bool get maintainState => true;這兩個屬性就保證了對話框的顯示效果以及和其他route一樣正常被系統(tǒng)處理。
再往上一級,DialogRoute繼承了RawDialogRoute,RawDialogRoute繼承自PopupRoute,這個類中首先把DialogRoute構(gòu)造時傳入的參數(shù)和ModalRoute中的屬性對應(yīng)起來:
//決定點擊遮蓋層是否可以pop該route @override bool get barrierDismissible => _barrierDismissible; final bool _barrierDismissible; //遮蓋層的顏色,這就是陰影區(qū)的顏色,可以通過給他設(shè)置透明度來達到半透明效果 @override Color? get barrierColor => _barrierColor; final Color? _barrierColor; //route顯示隱藏的時長 @override Duration get transitionDuration => _transitionDuration; final Duration _transitionDuration;然后重寫了ModalRoute的兩個方法:
@override Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { return Semantics( scopesRoute: true, explicitChildNodes: true, child: _pageBuilder(context, animation, secondaryAnimation), ); } @override Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { if (_transitionBuilder == null) { return FadeTransition( opacity: CurvedAnimation( parent: animation, curve: Curves.linear, ), child: child, ); } // Some default transition return _transitionBuilder!(context, animation, secondaryAnimation, child); }我們知道,在Flutter的路由流程中,最終就是通過buildPage方法來獲取route中的組件樹的,這里通過暴露的_pageBuilder函數(shù)來設(shè)置組件。buildTransitions是用來構(gòu)建顯示隱藏的動畫轉(zhuǎn)換效果。
通過上面的分析,我們發(fā)現(xiàn)這種實現(xiàn)要好了很多,至少我不需要在每個頁面的組件樹中插入Dialog組件了,但是還不夠優(yōu)雅,我們發(fā)現(xiàn)這里必須要傳入一個BuildContext,因為Navigator需要它,那么有什么方法解決這個問題呢?
-
get框架的實現(xiàn)
首先我們要明白,Navigator中需要的BuildContext必須是當前顯示的route的context,因為我們要根據(jù)它才能知道我們的新route放在哪里。
get框架中對dialog的使用也做了封裝,使用時不用再傳入BuildContext了,這就使得我們可以在非BuildContext持有類中操作Dialog,我們看一下他是怎么解決這個問題的。
get通過Get.dialog方法來顯示一個Dialog,通過Get.back方法來關(guān)閉一個Dialog,dialog方法中:
return generalDialog<T>( pageBuilder: (buildContext, animation, secondaryAnimation) { final pageChild = widget; Widget dialog = Builder(builder: (context) { return Theme(data: theme, child: pageChild); }); if (useSafeArea) { dialog = SafeArea(child: dialog); } return dialog; }, barrierDismissible: barrierDismissible, barrierLabel: MaterialLocalizations.of(context!).modalBarrierDismissLabel, barrierColor: barrierColor ?? Colors.black54, transitionDuration: transitionDuration ?? defaultDialogTransitionDuration, transitionBuilder: (context, animation, secondaryAnimation, child) { return FadeTransition( opacity: CurvedAnimation( parent: animation, curve: transitionCurve ?? defaultDialogTransitionCurve, ), child: child, ); }, navigatorKey: navigatorKey, routeSettings: routeSettings ?? RouteSettings(arguments: arguments, name: name), );generalDialog方法如下:
Future<T?> generalDialog<T>({ required RoutePageBuilder pageBuilder, bool barrierDismissible = false, String? barrierLabel, Color barrierColor = const Color(0x80000000), Duration transitionDuration = const Duration(milliseconds: 200), RouteTransitionsBuilder? transitionBuilder, GlobalKey<NavigatorState>? navigatorKey, RouteSettings? routeSettings, }) { assert(!barrierDismissible || barrierLabel != null); final nav = navigatorKey?.currentState ?? Navigator.of(overlayContext!, rootNavigator: true); //overlay context will always return the root navigator return nav.push<T>( GetDialogRoute<T>( pageBuilder: pageBuilder, barrierDismissible: barrierDismissible, barrierLabel: barrierLabel, barrierColor: barrierColor, transitionDuration: transitionDuration, transitionBuilder: transitionBuilder, settings: routeSettings, ), ); }可見,get同樣是通過Navigator來實現(xiàn)的,我們來看它的BuildContext問題是怎么不需要傳遞的。
我們發(fā)現(xiàn)它傳入了一個overlayContext:
BuildContext? get overlayContext { BuildContext? overlay; key.currentState?.overlay?.context.visitChildElements((element) { overlay = element; }); return overlay; }key是什么:
GlobalKey<NavigatorState> get key => _getxController.key; //extension GetNavigation中 static GetMaterialController _getxController = GetMaterialController(); //GetMaterialController中 var _key = GlobalKey<NavigatorState>(debugLabel: 'Key Created by default');而這個key是在第一次使用Navigator的時候在mount方法生成的,也可以理解成根NavigatorState的GlobalKey,所以通過它獲取的context也就是根NavigatorState的Element,然后通過visitChildElements方法循環(huán),最終會得到一個最上層的Overlay。什么是Overlay,它就是上面說的遮蓋層,每一個route都有自己的遮蓋層,我們的組件樹最終就是要呈現(xiàn)在它上面。
所以原理就是通過根NavigatorState獲取最上層的(也就是當前顯示的)route的Element(也就是BuildContext),然后傳給Navigator來調(diào)起路由。
-
總結(jié)
現(xiàn)在我們知道了Flutter中Dialog是如何實現(xiàn)的,并且我們知道了怎么去優(yōu)化Dialog的使用讓它變得更優(yōu)雅。
現(xiàn)在,我們可以結(jié)合get框架封裝一個工具類來使用它:
class DialogUtil { static void show() { if (Get.isDialogOpen == true) { return; } Get.dialog( LoadingWidget(), barrierColor: Color.fromRGBO(0, 0, 0, 0.5), barrierDismissible: false, ); } static void dismiss() { if (Get.isDialogOpen == true) { Get.back(); } } }isDialogOpen在使用get進行路由跳轉(zhuǎn)時會記錄當前route是否是Dialog類型的,我們可以根據(jù)它來判斷當前dialog是否在展示,就是一個標識位,我們自己也不難實現(xiàn)。
Flutter優(yōu)化Dialog使用
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
相關(guān)閱讀更多精彩內(nèi)容
- 概述get的官方文檔上介紹說,它具有更快和更實際的路由管理,至于性能上是不是如他所說我暫時沒做比較,本文從初始化的...
- 相關(guān)帖子收藏 windows 打包上傳ios到app store 作為android開發(fā)者,直接用studio即可...
- Flutter 學習筆記-基礎(chǔ)篇 如果你要獲取與該筆記配套的源碼,請點擊這里[https://github.com...
- 邂逅FLutter 萬物皆是Widget 一般縮進2個空格 文字居中 Widget Center() Materi...
- 這章來聊聊flutter的路由管理,也可以理解為頁面導航,用來處理頁面之間的跳轉(zhuǎn)、參數(shù)傳遞、動畫展示等功能。 路由...