系统自带的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一些信息