系統自帶的Dialog實際上就是Push了一個新頁面,這樣存在不少好處,可是也存在一些很難解決的問題html
必須傳BuildContextgit
沒法穿透暗色背景,點擊dialog後面的頁面github
系統自帶Dialog寫成的Loading彈窗,在網絡請求和跳轉頁面的狀況,會存在路由混亂的狀況web
上面這些痛點,簡直個個致命
,固然,還存在一些其它的解決方案,例如:redux
很明顯,使用Overlay可移植性最好,目前不少Toast和dialog三方庫即是使用該方案,使用了一些loading庫,看了其中源碼,穿透背景解決方案,和預期想要的效果截然不同、一些dialog庫自帶toast顯示,可是toast顯示卻又不能和dialog共存(toast屬於特殊的信息展現,理應能獨立存在),致使我須要多依賴一個Toast庫網絡
基於上面那些難以解決的問題,只能本身去實現,花了一些時間,實現了一個Pub包,基本該解決的痛點都已解決了,用於實際業務沒什麼問題app
dependencies: flutter_smart_dialog: any
主入口配置框架
void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: SmartDialogPage(), builder: (BuildContext context, Widget child) { return Material( type: MaterialType.transparency, child: FlutterSmartDialog(child: child), ); }, ); } }
使用FlutterSmartDialog
包裹下child便可,下面就能夠愉快的使用SmartDialog了less
使用Toast異步
SmartDialog.showToast('test toast');
使用Loading
//open loading SmartDialog.showLoading(); //delay off await Future.delayed(Duration(seconds: 2)); SmartDialog.dismiss();
自定義dialog
isUseExtraWidget
:是否使用額外覆蓋浮層,可與主浮層獨立開;可與loading,dialog之類獨立開,自帶的showToast即是開啓了該配置,可與loading共存SmartDialog.show( alignmentTemp: Alignment.bottomCenter, clickBgDismissTemp: true, widget: Container( color: Colors.blue, height: 300, ), );
SmartDialog配置參數說明
instance
裏面暴露過多屬性,致使使用不便,此處諸多參數使用instance
中的config
屬性管理參數 | 功能說明 |
---|---|
alignment | 控制自定義控件位於屏幕的位置<br/>Alignment.center: 自定義控件位於屏幕中間,且是動畫默認爲:漸隱和縮放,可以使用isLoading選擇動畫<br/>Alignment.bottomCenter、Alignment.bottomLeft、Alignment.bottomRight:自定義控件位於屏幕底部,動畫默認爲位移動畫,自下而上,可以使用animationDuration設置動畫時間<br/>Alignment.topCenter、Alignment.topLeft、Alignment.topRight:自定義控件位於屏幕頂部,動畫默認爲位移動畫,自上而下,可以使用animationDuration設置動畫時間<br/>Alignment.centerLeft:自定義控件位於屏幕左邊,動畫默認爲位移動畫,自左而右,可以使用animationDuration設置動畫時間<br/> Alignment.centerRight:自定義控件位於屏幕左邊,動畫默認爲位移動畫,自右而左,可以使用animationDuration設置動畫時間 |
isPenetrate | 默認:false;是否穿透遮罩背景,交互遮罩以後控件,true:點擊能穿透背景,false:不能穿透;穿透遮罩設置爲true,背景遮罩會自動變成透明(必須) |
clickBgDismiss | 默認:false;點擊遮罩,是否關閉dialog---true:點擊遮罩關閉dialog,false:不關閉 |
maskColor | 遮罩顏色 |
animationDuration | 動畫時間 |
isUseAnimation | 默認:true;是否使用動畫 |
isLoading | 默認:true;是否使用Loading動畫;true:內容體使用漸隱動畫 false:內容體使用縮放動畫,僅僅針對中間位置的控件 |
isExist | 默認:false;主體SmartDialog(OverlayEntry)是否存在在界面上 |
isExistExtra | 默認:false;額外SmartDialog(OverlayEntry)是否存在在界面上 |
Config屬性使用,舉個栗子
SmartDialog.instance.config ..clickBgDismiss = true ..isLoading = true ..isUseAnimation = true ..animationDuration = Duration(milliseconds: 270) ..isPenetrate = true ..maskColor = Colors.black.withOpacity(0.1) ..isExist = false ..isExistExtra = false ..alignment = Alignment.center;
使用Overlay的依賴庫,基本都存在一個問題,難以對返回事件的監聽,致使觸犯返回事件難以關閉彈窗佈局之類,想了不少辦法,沒辦法在依賴庫中解決該問題,此處提供一個BaseScaffold
,在每一個頁面使用BaseScaffold
,便能解決返回事件關閉Dialog問題
typedef ScaffoldParamVoidCallback = void Function(); class BaseScaffold extends StatefulWidget { const BaseScaffold({ Key key, this.appBar, this.body, this.floatingActionButton, this.floatingActionButtonLocation, this.floatingActionButtonAnimator, this.persistentFooterButtons, this.drawer, this.endDrawer, this.bottomNavigationBar, this.bottomSheet, this.backgroundColor, this.resizeToAvoidBottomPadding, this.resizeToAvoidBottomInset, this.primary = true, this.drawerDragStartBehavior = DragStartBehavior.start, this.extendBody = false, this.extendBodyBehindAppBar = false, this.drawerScrimColor, this.drawerEdgeDragWidth, this.drawerEnableOpenDragGesture = true, this.endDrawerEnableOpenDragGesture = true, this.isTwiceBack = false, this.isCanBack = true, this.onBack, }) : assert(primary != null), assert(extendBody != null), assert(extendBodyBehindAppBar != null), assert(drawerDragStartBehavior != null), super(key: key); ///系統Scaffold的屬性 final bool extendBody; final bool extendBodyBehindAppBar; final PreferredSizeWidget appBar; final Widget body; final Widget floatingActionButton; final FloatingActionButtonLocation floatingActionButtonLocation; final FloatingActionButtonAnimator floatingActionButtonAnimator; final List<Widget> persistentFooterButtons; final Widget drawer; final Widget endDrawer; final Color drawerScrimColor; final Color backgroundColor; final Widget bottomNavigationBar; final Widget bottomSheet; final bool resizeToAvoidBottomPadding; final bool resizeToAvoidBottomInset; final bool primary; final DragStartBehavior drawerDragStartBehavior; final double drawerEdgeDragWidth; final bool drawerEnableOpenDragGesture; final bool endDrawerEnableOpenDragGesture; ///增長的屬性 ///點擊返回按鈕提示是否退出頁面,快速點擊倆次纔會退出頁面 final bool isTwiceBack; ///是否能夠返回 final bool isCanBack; ///監聽返回事件 final ScaffoldParamVoidCallback onBack; @override _BaseScaffoldState createState() => _BaseScaffoldState(); } class _BaseScaffoldState extends State<BaseScaffold> { //上次點擊時間 DateTime _lastPressedAt; @override Widget build(BuildContext context) { return WillPopScope( child: Scaffold( appBar: widget.appBar, body: widget.body, floatingActionButton: widget.floatingActionButton, floatingActionButtonLocation: widget.floatingActionButtonLocation, floatingActionButtonAnimator: widget.floatingActionButtonAnimator, persistentFooterButtons: widget.persistentFooterButtons, drawer: widget.drawer, endDrawer: widget.endDrawer, bottomNavigationBar: widget.bottomNavigationBar, bottomSheet: widget.bottomSheet, backgroundColor: widget.backgroundColor, resizeToAvoidBottomPadding: widget.resizeToAvoidBottomPadding, resizeToAvoidBottomInset: widget.resizeToAvoidBottomInset, primary: widget.primary, drawerDragStartBehavior: widget.drawerDragStartBehavior, extendBody: widget.extendBody, extendBodyBehindAppBar: widget.extendBodyBehindAppBar, drawerScrimColor: widget.drawerScrimColor, drawerEdgeDragWidth: widget.drawerEdgeDragWidth, drawerEnableOpenDragGesture: widget.drawerEnableOpenDragGesture, endDrawerEnableOpenDragGesture: widget.endDrawerEnableOpenDragGesture, ), onWillPop: _dealWillPop, ); } ///控件返回按鈕 Future<bool> _dealWillPop() async { if (widget.onBack != null) { widget.onBack(); } //處理彈窗問題 if (SmartDialog.instance.config.isExist) { SmartDialog.dismiss(); return false; } //若是不能返回,後面的邏輯就不走了 if (!widget.isCanBack) { return false; } if (widget.isTwiceBack) { if (_lastPressedAt == null || DateTime.now().difference(_lastPressedAt) > Duration(seconds: 1)) { //兩次點擊間隔超過1秒則從新計時 _lastPressedAt = DateTime.now(); //彈窗提示 SmartDialog.showToast("再點一次退出"); return false; } return true; } else { return true; } } }
當時想解決穿透暗色背景,和背景後面的控件互動的時候,我幾乎立馬想到這倆個控件,先了解下這倆個控件吧
AbsorbPointer
AbsorbPointer
自己能夠響應事件,消耗掉事件absorbing
屬性(默認true)
AbsorbPointer( absorbing: true, child: Listener( onPointerDown: (event){ print('+++++++++++++++++++++++++++++++++'); }, ) )
IgnorePointer
IgnorePointer
自己沒法響應事件,其下的控件能夠接收到點擊事件(父控件)ignoring
屬性(默認true)
IgnorePointer( ignoring: true, child: Listener( onPointerDown: (event){ print('----------------------------------'); }, ) )
分析
AbsorbPointer
這個控件是不合適的,由於AbsorbPointer
自己會消費觸摸事件,事件被AbsorbPointer
消費掉,會致使背景後的頁面沒法獲取到觸摸事件;IgnorePointer
自己沒法消費觸摸事件,又因爲IgnorePointer
和AbsorbPointer
都具備屏蔽子Widget獲取觸摸事件的做用,這個貌似靠譜,這裏試了,能夠和背景後面的頁面互動!可是又存在一個十分坑的問題IgnorePointer
屏蔽子控件的觸摸事件,而IgnorePointer
自己又不消耗觸摸事件,會致使沒法獲取到背景的點擊事件!這樣點擊背景會沒法關閉dialog彈窗,只能手動關閉dialog;各類嘗試,實在沒辦法獲取到背景的觸摸事件,此種穿透背景的方案只能放棄這種方案,成功實現想要的穿透效果,這裏瞭解下behavior
的幾種屬性
有戲了!很明顯translucent是有但願的,嘗試了幾回,而後成功實現了想要的效果
注意,這邊有幾個坑點,提一下
Listener
控件來使用behavior屬性,使用GestureDetector中behavior屬性會存在一個問題,通常來講:都是Stack控件裏面的Children,裏面有倆個控件,分上下層,在此處,GestureDetector設置behavior屬性,倆個GestureDetector控件上下疊加,會致使下層GestureDetector獲取不到觸摸事件,很奇怪;使用Listener
不會產生此問題Container
控件,我這裏設置了Colors.transparent
,直接會致使下層接受不到觸摸事件,color爲空才能使下層控件接受到觸摸事件,此處不要設置color便可下面是寫的一個驗證小示例
class TestLayoutPage extends StatelessWidget { @override Widget build(BuildContext context) { return _buildBg(children: [ //下層 Listener( onPointerDown: (event) { print('下層藍色區域++++++++'); }, child: Container( height: 300, width: 300, color: Colors.blue, ), ), //上層 事件穿透 Listener( behavior: HitTestBehavior.translucent, onPointerDown: (event) { print('上層區域---------'); }, child: Container( height: 200, width: 200, ), ), ]); } Widget _buildBg({List<Widget> children}) { return Scaffold( appBar: AppBar(title: Text('測試佈局')), body: Center( child: Stack( alignment: Alignment.center, children: children, ), ), ); } }
Toast明顯是應該獨立於其餘彈窗的一個消息提示,封裝在網絡庫中的關閉彈窗的dismiss方法,也會將Toast消息在不適宜的時候關閉,在實際開發中就碰到此問題,只能多引用一個Toast三方庫來解決,在規劃這個dialog庫的時候,就想到必須解決此問題
OverlayEntry
和OverlayEntryExtra
能夠高度自定義,相關實現,可查看內部實現show()
方法中的isUseExtraWidget
區分這個庫花了一些時間去構思和實現,算是解決幾個很大的痛點
返回事件
有什麼好的處理思路,麻煩在評論裏告知,謝謝!FlutterSmartDialog一些信息