為了應(yīng)對(duì)復(fù)雜的業(yè)務(wù)場(chǎng)景,同時(shí)降低侵入性,在保持api穩(wěn)定基礎(chǔ)上,全面重構(gòu)了SmartDialog底層
我現(xiàn)在可以自信的說(shuō):它現(xiàn)在是一個(gè)簡(jiǎn)潔,強(qiáng)大,侵入性極低的Pub包
請(qǐng)使用Flutter 2.0及其以上的小伙伴們移步:這一次,解決Flutter Dialog的各種痛點(diǎn)!
前言
系統(tǒng)自帶的Dialog實(shí)際上就是Push了一個(gè)新頁(yè)面,這樣存在很多好處,但是也存在一些很難解決的問(wèn)題
-
必須傳BuildContext
- loading彈窗一般都封裝在網(wǎng)絡(luò)框架中,多傳個(gè)context參數(shù)就很頭疼;用fish_redux還好,effect層直接能拿到context,要是用bloc還得在view層把context傳到bloc或者cubit里面。。。
-
無(wú)法穿透暗色背景,點(diǎn)擊dialog后面的頁(yè)面
- 這個(gè)是真頭痛,想了很多辦法,都沒(méi)能在自帶的dialog上面解決這個(gè)問(wèn)題
-
系統(tǒng)自帶Dialog寫(xiě)成的Loading彈窗,在網(wǎng)絡(luò)請(qǐng)求和跳轉(zhuǎn)頁(yè)面的情況,會(huì)存在路由混亂的情況
- 情景復(fù)盤(pán):loading庫(kù)一般封裝在網(wǎng)絡(luò)層,某個(gè)頁(yè)面提交完表單,要跳轉(zhuǎn)頁(yè)面,提交操作完成,進(jìn)行頁(yè)面跳轉(zhuǎn),loading關(guān)閉是在異步回調(diào)中進(jìn)行(onError或者onSuccess),會(huì)出現(xiàn)執(zhí)行了跳轉(zhuǎn)操作時(shí),彈窗還未關(guān)閉,延時(shí)一小會(huì)關(guān)閉,因?yàn)橛玫亩际莗op頁(yè)面方法,會(huì)把跳轉(zhuǎn)的頁(yè)面pop掉
- 上面是一種很常見(jiàn)的場(chǎng)景,涉及到復(fù)雜場(chǎng)景更加難以預(yù)測(cè),解決方法也有:定位頁(yè)面棧的棧頂是否是Loading彈窗,選擇性Pop,實(shí)現(xiàn)麻煩
上面這些痛點(diǎn),簡(jiǎn)直個(gè)個(gè)致命,當(dāng)然,還存在一些其它的解決方案,例如:
- 頁(yè)面頂級(jí)使用Stack
- 使用Overlay
很明顯,使用Overlay可移植性最好,目前很多toast和dialog三方庫(kù)便是使用該方案,使用了一些loading庫(kù),看了其中源碼,穿透背景解決方案,和預(yù)期想要的效果大相徑庭、一些dialog庫(kù)自帶toast顯示,但是toast顯示卻又不能和dialog共存(toast屬于特殊的信息展示,理應(yīng)能獨(dú)立存在),導(dǎo)致我需要多依賴(lài)一個(gè)Toast庫(kù)
SmartDialog
基于上面那些難以解決的問(wèn)題,只能自己去實(shí)現(xiàn),花了一些時(shí)間,實(shí)現(xiàn)了一個(gè)Pub包,基本該解決的痛點(diǎn)都已解決了,用于實(shí)際業(yè)務(wù)沒(méi)什么問(wèn)題
效果

引入
-
Pub:查看flutter_smart_dialog插件版本
- 自2.0版本開(kāi)始,本庫(kù)已適配空安全
dependencies:
flutter_smart_dialog: any
- 注意:該庫(kù)已遷移空安全,注意版本區(qū)分
# 非空安全前最后一個(gè)穩(wěn)定版本
dependencies:
flutter_smart_dialog: ^1.3.1
使用
- 主入口配置
- 在主入口這地方需要配置下,這樣就可以不傳BuildContext使用Dialog了
- 只需要在MaterialApp的builder參數(shù)處配置下即可
void main() {
runApp(MyApp());
}
///flutter 2.0
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Container(),
builder: (BuildContext context, Widget? child) {
return FlutterSmartDialog(child: child);
},
);
}
}
///flutter 1.x
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Container(),
builder: (BuildContext context, Widget child) {
return FlutterSmartDialog(child: child);
},
);
}
}
使用FlutterSmartDialog包裹下child即可,下面就可以愉快的使用SmartDialog了
- 使用Toast:因?yàn)閠oast特殊性,此處單獨(dú)對(duì)toast做了一些優(yōu)化
- msg:必傳參數(shù)
- time:可選,Duration類(lèi)型,默認(rèn)2000ms
- widget:可選,可以自定義toast
- alignment:可選,控制toast位置
- 如果想使用花里胡哨的Toast效果,請(qǐng)使用showToast方法定制就行了,炒雞簡(jiǎn)單喔,懶得自己寫(xiě)的,抄下我的ToastWidget,改下屬性就行了哈
SmartDialog.showToast('test toast');
- 使用Loading:loading擁有諸多設(shè)置屬性,參照下方的
SmartDialog配置參數(shù)說(shuō)明即可- msg:可選,loading動(dòng)畫(huà)下面的文字信息(默認(rèn):加載中...)
//open loading
SmartDialog.showLoading();
//delay off
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss();
- 自定義dialog
- 使用SmartDialog.show()方法即可,里面含有眾多
Temp為后綴的參數(shù),和下述無(wú)Temp為后綴的參數(shù)功能一致 - 特殊屬性
isUseExtraWidget:是否使用額外覆蓋浮層,可與主浮層獨(dú)立開(kāi);可與loading,dialog之類(lèi)獨(dú)立開(kāi),自帶的showToast便是開(kāi)啟了該配置,可與loading共存
- 使用SmartDialog.show()方法即可,里面含有眾多
SmartDialog.show(
alignmentTemp: Alignment.bottomCenter,
clickBgDismissTemp: true,
widget: Container(
color: Colors.blue,
height: 300,
),
);
- SmartDialog配置參數(shù)說(shuō)明
- 為了避免
instance里面暴露過(guò)多屬性,導(dǎo)致使用不便,此處諸多參數(shù)使用instance中的config屬性管理 - 使用config設(shè)置的屬性都是全局的,將這些屬性單獨(dú)使用Config管理,是為了方便修改和管理這些屬性,也是為了使SmartDialog類(lèi)更易維護(hù)
- 為了避免
| 參數(shù) | 功能說(shuō)明 |
|---|---|
| alignment | 控制自定義控件位于屏幕的位置 Alignment.center: 自定義控件位于屏幕中間,且是動(dòng)畫(huà)默認(rèn)為:漸隱和縮放,可使用isLoading選擇動(dòng)畫(huà) Alignment.bottomCenter、Alignment.bottomLeft、Alignment.bottomRight:自定義控件位于屏幕底部,動(dòng)畫(huà)默認(rèn)為位移動(dòng)畫(huà),自下而上,可使用animationDuration設(shè)置動(dòng)畫(huà)時(shí)間 Alignment.topCenter、Alignment.topLeft、Alignment.topRight:自定義控件位于屏幕頂部,動(dòng)畫(huà)默認(rèn)為位移動(dòng)畫(huà),自上而下,可使用animationDuration設(shè)置動(dòng)畫(huà)時(shí)間 Alignment.centerLeft:自定義控件位于屏幕左邊,動(dòng)畫(huà)默認(rèn)為位移動(dòng)畫(huà),自左而右,可使用animationDuration設(shè)置動(dòng)畫(huà)時(shí)間 Alignment.centerRight:自定義控件位于屏幕左邊,動(dòng)畫(huà)默認(rèn)為位移動(dòng)畫(huà),自右而左,可使用animationDuration設(shè)置動(dòng)畫(huà)時(shí)間 |
| isPenetrate | 默認(rèn):false;是否穿透遮罩背景,交互遮罩之后控件,true:點(diǎn)擊能穿透背景,false:不能穿透;穿透遮罩設(shè)置為true,背景遮罩會(huì)自動(dòng)變成透明(必須) |
| clickBgDismiss | 默認(rèn):true;點(diǎn)擊遮罩,是否關(guān)閉dialog---true:點(diǎn)擊遮罩關(guān)閉dialog,false:不關(guān)閉 |
| maskColor | 遮罩顏色(isPenetrate為true,該參數(shù)失效) |
| maskWidget | 可高度自定義遮罩樣式,使用該參數(shù),maskColor失效(isPenetrate為true,該參數(shù)失效) |
| animationDuration | 動(dòng)畫(huà)時(shí)間 |
| isUseAnimation | 默認(rèn):true;是否使用動(dòng)畫(huà) |
| isLoading | 默認(rèn):true;是否使用Loading動(dòng)畫(huà);true:內(nèi)容體使用漸隱動(dòng)畫(huà) false:內(nèi)容體使用縮放動(dòng)畫(huà),僅僅針對(duì)中間位置的dialog |
| isExist | 狀態(tài)標(biāo)定:loading和自定義dialog 是否存在在界面上 |
| isExistMain | 狀態(tài)標(biāo)定:自定義dialog 是否存在在界面上(show) |
| isExistLoading | 狀態(tài)標(biāo)定:loading是否存在界面上(showLoading) |
| isExistToast | 狀態(tài)標(biāo)定:toast是否存在在界面上(showToast) |
- Config屬性使用,舉個(gè)栗子
- 內(nèi)部已初始化相關(guān)屬性;如果需要定制,可在主入口處,初始化自己想要的屬性
SmartDialog.instance.config
..clickBgDismiss = true
..isLoading = true
..isUseAnimation = true
..animationDuration = Duration(milliseconds: 270)
..isPenetrate = false
..maskColor = Colors.black.withOpacity(0.1)
..alignment = Alignment.center;
- 返回事件,關(guān)閉彈窗解決方案
使用Overlay的依賴(lài)庫(kù),基本都存在一個(gè)問(wèn)題,難以對(duì)返回事件的監(jiān)聽(tīng),導(dǎo)致觸犯返回事件難以關(guān)閉彈窗布局之類(lèi),想了很多辦法,沒(méi)辦法在依賴(lài)庫(kù)中解決該問(wèn)題,此處提供一個(gè)BaseScaffold,在每個(gè)頁(yè)面使用BaseScaffold,便能解決返回事件關(guān)閉Dialog問(wèn)題
- Flutter 2.0
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.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,
}) : super(key: key);
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? resizeToAvoidBottomInset;
final bool primary;
final DragStartBehavior drawerDragStartBehavior;
final double? drawerEdgeDragWidth;
final bool drawerEnableOpenDragGesture;
final bool endDrawerEnableOpenDragGesture;
//custom param
final bool isTwiceBack;
final bool isCanBack;
final ScaffoldParamVoidCallback? onBack;
@override
_BaseScaffoldState createState() => _BaseScaffoldState();
}
class _BaseScaffoldState extends State<BaseScaffold> {
DateTime? _lastTime;
@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,
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 {
widget.onBack?.call();
if (SmartDialog.instance.config.isExist) {
SmartDialog.dismiss();
return false;
}
if (!widget.isCanBack) {
return false;
}
var now = DateTime.now();
var condition =
_lastTime == null || now.difference(_lastTime!) > Duration(seconds: 1);
if (widget.isTwiceBack && condition) {
_lastTime = now;
SmartDialog.showToast("再點(diǎn)一次退出");
return false;
}
return true;
}
}
- Flutter 1.x
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.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);
///系統(tǒng)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 resizeToAvoidBottomInset;
final bool primary;
final DragStartBehavior drawerDragStartBehavior;
final double drawerEdgeDragWidth;
final bool drawerEnableOpenDragGesture;
final bool endDrawerEnableOpenDragGesture;
///增加的屬性
///點(diǎn)擊返回按鈕提示是否退出頁(yè)面,快速點(diǎn)擊倆次才會(huì)退出頁(yè)面
final bool isTwiceBack;
///是否可以返回
final bool isCanBack;
///監(jiān)聽(tīng)返回事件
final ScaffoldParamVoidCallback onBack;
@override
_BaseScaffoldState createState() => _BaseScaffoldState();
}
class _BaseScaffoldState extends State<BaseScaffold> {
DateTime _lastPressedAt; //上次點(diǎn)擊時(shí)間
@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,
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();
}
//處理彈窗問(wèn)題
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)) {
//兩次點(diǎn)擊間隔超過(guò)1秒則重新計(jì)時(shí)
_lastPressedAt = DateTime.now();
//彈窗提示
SmartDialog.showToast("再點(diǎn)一次退出");
return false;
}
return true;
} else {
return true;
}
}
}
幾個(gè)問(wèn)題解決方案
穿透背景
- 穿透背景有倆個(gè)解決方案,這里都說(shuō)明下
AbsorbPointer、IgnorePointer
當(dāng)時(shí)想解決穿透暗色背景,和背景后面的控件互動(dòng)的時(shí)候,我?guī)缀趿ⅠR想到這倆個(gè)控件,先了解下這倆個(gè)控件吧
- AbsorbPointer
阻止子樹(shù)接收指針事件,
AbsorbPointer本身可以響應(yīng)事件,消耗掉事件-
absorbing屬性(默認(rèn)true)- true:攔截向子Widget傳遞的事件 false:不攔截
AbsorbPointer(
absorbing: true,
child: Listener(
onPointerDown: (event){
print('+++++++++++++++++++++++++++++++++');
},
)
)
- IgnorePointer
- 阻止子樹(shù)接收指針事件,
IgnorePointer本身無(wú)法響應(yīng)事件,其下的控件可以接收到點(diǎn)擊事件(父控件) -
ignoring屬性(默認(rèn)true)- true:攔截向子Widget傳遞的事件 false:不攔截
- 阻止子樹(shù)接收指針事件,
IgnorePointer(
ignoring: true,
child: Listener(
onPointerDown: (event){
print('----------------------------------');
},
)
)
分析
- 這里來(lái)分析下,首先
AbsorbPointer這個(gè)控件是不合適的,因?yàn)?code>AbsorbPointer本身會(huì)消費(fèi)觸摸事件,事件被AbsorbPointer消費(fèi)掉,會(huì)導(dǎo)致背景后的頁(yè)面無(wú)法獲取到觸摸事件;IgnorePointer本身無(wú)法消費(fèi)觸摸事件,又由于IgnorePointer和AbsorbPointer都具有屏蔽子Widget獲取觸摸事件的作用,這個(gè)貌似靠譜,這里試了,可以和背景后面的頁(yè)面互動(dòng)!但是又存在一個(gè)十分坑的問(wèn)題 - 因?yàn)槭褂?code>IgnorePointer屏蔽子控件的觸摸事件,而
IgnorePointer本身又不消耗觸摸事件,會(huì)導(dǎo)致無(wú)法獲取到背景的點(diǎn)擊事件!這樣點(diǎn)擊背景會(huì)無(wú)法關(guān)閉dialog彈窗,只能手動(dòng)關(guān)閉dialog;各種嘗試,實(shí)在沒(méi)辦法獲取到背景的觸摸事件,此種穿透背景的方案只能放棄
Listener、behavior
這種方案,成功實(shí)現(xiàn)想要的穿透效果,這里了解下behavior的幾種屬性
- deferToChild:僅當(dāng)一個(gè)孩子被命中測(cè)試擊中時(shí),屈服于其孩子的目標(biāo)才會(huì)在其范圍內(nèi)接收事件
- opaque:不透明目標(biāo)可能會(huì)受到命中測(cè)試的打擊,導(dǎo)致它們既在其范圍內(nèi)接收事件,又在視覺(jué)上阻止位于其后方的目標(biāo)也接收事件
- translucent:半透明目標(biāo)既可以接收其范圍內(nèi)的事件,也可以在視覺(jué)上允許目標(biāo)后面的目標(biāo)也接收事件
有戲了!很明顯translucent是有希望的,嘗試了幾次,然后成功實(shí)現(xiàn)了想要的效果
注意,這邊有幾個(gè)坑點(diǎn),提一下
務(wù)必使用
Listener控件來(lái)使用behavior屬性,使用GestureDetector中behavior屬性會(huì)存在一個(gè)問(wèn)題,一般來(lái)說(shuō):都是Stack控件里面的Children,里面有倆個(gè)控件,分上下層,在此處,GestureDetector設(shè)置behavior屬性,倆個(gè)GestureDetector控件上下疊加,會(huì)導(dǎo)致下層GestureDetector獲取不到觸摸事件,很奇怪;使用Listener不會(huì)產(chǎn)生此問(wèn)題我們的背景使用
Container控件,我這里設(shè)置了Colors.transparent,直接會(huì)導(dǎo)致下層接受不到觸摸事件,color為空才能使下層控件接受到觸摸事件,此處不要設(shè)置color即可
下面是寫(xiě)的一個(gè)驗(yàn)證小示例
class TestLayoutPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _buildBg(children: [
//下層
Listener(
onPointerDown: (event) {
print('下層藍(lán)色區(qū)域++++++++');
},
child: Container(
height: 300,
width: 300,
color: Colors.blue,
),
),
//上層 事件穿透
Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: (event) {
print('上層區(qū)域---------');
},
child: Container(
height: 200,
width: 200,
),
),
]);
}
Widget _buildBg({List<Widget> children}) {
return Scaffold(
appBar: AppBar(title: Text('測(cè)試布局')),
body: Center(
child: Stack(
alignment: Alignment.center,
children: children,
),
),
);
}
}
Toast和Loading沖突
這個(gè)問(wèn)題,從理論上肯定會(huì)存在的,因?yàn)橐话鉕verlay庫(kù)只會(huì)使用一個(gè)OverlayEntry控件,這會(huì)導(dǎo)致,全局只能存在一個(gè)浮窗布局,Toast本質(zhì)是一個(gè)全局彈窗,Loading也是一個(gè)全局彈窗,使用其中一個(gè)都會(huì)導(dǎo)致另一個(gè)消失
-
Toast明顯是應(yīng)該獨(dú)立于其他彈窗的一個(gè)消息提示,封裝在網(wǎng)絡(luò)庫(kù)中的關(guān)閉彈窗的dismiss方法,也會(huì)將Toast消息在不適宜的時(shí)候關(guān)閉,在實(shí)際開(kāi)發(fā)中就碰到此問(wèn)題,只能多引用一個(gè)Toast三方庫(kù)來(lái)解決,在規(guī)劃這個(gè)dialog庫(kù)的時(shí)候,就想到必須解決此問(wèn)題
- 此處內(nèi)部多使用了一個(gè)OverlayEntry來(lái)解決該問(wèn)題,提供了相關(guān)參數(shù)來(lái)分別控制,完美使Toast獨(dú)立于其它的dialog彈窗
- 多增加一個(gè)OverlayEntry都會(huì)讓內(nèi)部邏輯和方法使用急劇復(fù)雜,維護(hù)也會(huì)變得不可預(yù)期,故額外只多提供一個(gè)OverlayEntry;如果需要更多,可copy本庫(kù),自行定義,實(shí)現(xiàn)該庫(kù)相關(guān)源碼,都力求能讓人看明白,相信大家copy使用時(shí)不會(huì)感到晦澀難懂
FlutterSmartDialog提供
OverlayEntry和OverlayEntryExtra可以高度自定義,相關(guān)實(shí)現(xiàn),可查看內(nèi)部實(shí)現(xiàn)FlutterSmartDialog內(nèi)部已進(jìn)行相關(guān)實(shí)現(xiàn),使用
show()方法中的isUseExtraWidget區(qū)分
最后
這個(gè)庫(kù)花了一些時(shí)間去構(gòu)思和實(shí)現(xiàn),算是解決幾個(gè)很大的痛點(diǎn)
- 如果大家對(duì)
返回事件有什么好的處理思路,麻煩在評(píng)論里告知,謝謝!
項(xiàng)目地址
FlutterSmartDialog一些信息
Github:flutter_smart_dialog
使用效果體驗(yàn):點(diǎn)擊體驗(yàn)一下
系列文章
狀態(tài)管理
中小項(xiàng)目墻裂推薦:Flutter GetX使用---簡(jiǎn)潔的魅力!
大型項(xiàng)目推薦:fish_redux使用詳解---看完就會(huì)用!