一種更優(yōu)雅的Flutter Dialog解決方案

為了應(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)題

效果

smartDialog

引入

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(
    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:不攔截
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)觸摸事件,又由于IgnorePointerAbsorbPointer都具有屏蔽子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提供OverlayEntryOverlayEntryExtra可以高度自定義,相關(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一些信息

系列文章

狀態(tài)管理

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容