• Flutter如何实现弹窗排队功能?
  • 发布于 2个月前
  • 491 热度
    0 评论
  • CEBBCt
  • 4 粉丝 44 篇博客
  •   
前言

弹窗的排队执行在App中是一个很常见的应用场景。比如进入App首页,一系列的弹窗就会弹出。如果不做处理就会导致弹窗一窝蜂的全部弹出,严重影响用户体验。如果多个弹窗中有判断逻辑,同意协议弹出另一个弹窗,不同意弹窗另外一个弹窗,又添加了复杂度了。所以才会有需要管理多个弹窗的展示时机,一般来说在一个Flutter App 中有下面几种方式来管理的。


一、队列的方式实现
其实最简单最原始的方案就是硬写,判断逻辑写在弹窗或Picker里面,一层一层的嵌套,反正布局都嵌套了,弹窗逻辑嵌套怕什么...
当然比较好的做法是用一个队列管理起来。
class DialogQueue {
  final Queue<AlertDialog> _dialogQueue = Queue();
  bool _isShowingDialog = false;
  // 堆代码 duidaima.com
  void show(AlertDialog dialog, BuildContext context) {
    _dialogQueue.add(dialog);
    if (!_isShowingDialog) {
      _isShowingDialog = true;
      _showNext(context);
    }
  }

  void _showNext(BuildContext context) {
    if (_dialogQueue.isNotEmpty) {
      final dialog = _dialogQueue.removeFirst();
      showDialog<void>(
        context: context,
        builder: (BuildContext context) {
          return dialog;
        },
      ).then((_) {
        if (_dialogQueue.isNotEmpty) {
          _showNext(context);
        } else {
          _isShowingDialog = false;
        }
      });
    }
  }
}
使用的时候,往队列里面添加即可:
        ElevatedButton(
            onPressed: () {
                _dialogQueue.show(
                AlertDialog(
                    title: Text('Dialog 1'),
                    content: Text('This is the first dialog'),
                ),
                context, 
                );
            },
            child: Text('Show Dialog 1'),
        ),
        ElevatedButton(
            onPressed: () {
                _dialogQueue.show(
                AlertDialog(
                    title: Text('Dialog 2'),
                    content: Text('This is the second dialog'),
                ),
                context, 
                );
            },
               child: Text('Show Dialog 2'),
        ),

代码很简单,但这样虽然能做到排队执行,但是有几个很大的问题,不够灵活,不能控制其中哪一个不显示或不显示(实现起来比较麻烦),其次就是只支持 Dialog,不支持用 Toast SnackBar Picker等其他的用法。总结一句话就是不够灵活。那么我们之前使用过值拦截式的拦截器实现在 Android 平台已经实现过了。那么我们移植到 Flutter 平台能不能实现对应的功能呢?


二、拦截器的方式实现
与前文拦截器实现登录拦截的方式有所区别的是通过值传递方式我们需要定义一个泛型,把传递的对象定义一下。
class InterceptChain<T> {
  InterceptChain? next;

  void intercept(T data) {
    next?.intercept(data);
  }
}
管理类:
class InterceptChainHandler<T> {
  InterceptChain? _interceptFirst;

  void add(InterceptChain interceptChain) {
    if (_interceptFirst == null) {
      _interceptFirst = interceptChain;
      return;
    }

    InterceptChain? node = _interceptFirst;

    while (true) {
      if (node!.next == null) {
        node.next = interceptChain;
        break;
      }
      node = node.next;
    }
  }

  void intercept(T data) {
    _interceptFirst?.intercept(data);
  }
}
与之前拦截登录的方式不同的是,这种方案没有指定的一些拦截器,更加的灵活,使用起来我们就需要定义每一个 InterceptChain ,也就是每一个 Dialog/Picker等效果都可以是一个 Intercept 。
class TipsIntercept extends InterceptChain<DialogPass> {
  @override
  void intercept(DialogPass data) {
    SmartDialog.show(
      usePenetrate: false,
      builder: (context) =>
          AppDefaultDialog(
            "你确定要发送验证码吗?",
            confirmAction: () {
              super.intercept(data);
            },
          ),
    );
  }
}

class VerifyIntercept extends InterceptChain<DialogPass> {
  @override
  void intercept(DialogPass data) {
    SmartDialog.show(
      usePenetrate: false,
      builder: (context) => VerifyCodeDialog(
        confirmAction: () {
          super.intercept(data);
        },
      ),
    );
  }
}

class OneIntercept extends InterceptChain<DialogPass> {
  @override
  void intercept(DialogPass data) {
    if (data != null) {
      final interceptData = '当前的Data1:${data.msg}';
      print(interceptData);

      if (data.passType == 1) {
        // 弹窗1
        SmartDialog.show(
          usePenetrate: false,
          builder: (context) => AppDefaultDialog(
            data.msg ?? "",
            confirmAction: () {
              data.passType = 2;
              super.intercept(data);
            },
          ),
        );
      } else {
        super.intercept(data);
      }
    } else {
      super.intercept(data);
    }
  }
}

class TwoIntercept extends InterceptChain<DialogPass> {
  @override
  void intercept(DialogPass data) {
    if (data != null) {
      final interceptData = '当前的Data2:${data.msg}';
      print(interceptData);

      if (data.passType == 2) {
        // 弹窗2
        SmartDialog.show(
          usePenetrate: false,
          clickMaskDismiss: false,
          builder: (context) => PrivacyPolicyDialog(
            confirmAction: () {
              data.passType = 3;
              super.intercept(data);
            },
            cancelAction: () {
              super.intercept(data);
            },
          ),
        );
      } else {
        super.intercept(data);
      }
    } else {
      super.intercept(data);
    }
  }
}

class ThreeIntercept extends InterceptChain<DialogPass> {
  @override
  void intercept(DialogPass data) {
    if (data != null) {
      final interceptData = '当前的Data3:${data.msg}';
      print(interceptData);

      if (data.passType == 3) {
        // 弹窗2
        DataPickerUtil.showCupertinoDataPicker(
          items: ["全部","等待确认", "已完成"],
          onPickerChanged: (list, position) {
            super.intercept(data);
          },
        );
      } else {
        super.intercept(data);
      }
    } else {
      super.intercept(data);
    }
  }
}
这里简单的定义了5个弹窗/Picker效果,并且简单的定义了变量的控制对象为:
class DialogPass {
  String? msg;
  int passType = 0;

  DialogPass(this.msg, this.passType);
}
这个是一般是由后台服务器控制,请求接口之后可以动态的根据多个变量控制哪一个弹窗展示,并且可以控制一个弹窗展示之后它对应的后续弹窗的展示。可以很方便的通过对象中的变量控制。具体哪个弹窗是否展示,如何展示都是子 Intercept 控制,我们最终只需要把全部的拦截类添加到拦截器容器中即可:
 final intercepts = InterceptChainHandler<DialogPass>();
    intercepts.add(TipsIntercept());
    intercepts.add(VerifyIntercept());
    intercepts.add(OneIntercept());
    intercepts.add(TwoIntercept());
    intercepts.add(ThreeIntercept());

    intercepts.intercept(DialogPass('你确定你要阅读全部协议吗1?',1));
效果:

虽然代码是多一个但是相对而言就灵活一点。


后记

用队列虽然也能实现一些特定的场景需求,但是我还是更推荐拦截器的方式,相对而言更灵活。所以说一些在 iOS/Android 中成熟使用的一些设计模式真的是可以很方便的解决一些业务场景的痛点,并不限制于语言与平台。现在我们应用中的首页的弹窗与申请工作的弹窗校验就能很方便的用拦截器的方式进行管理了。并且用值传递的方式我们也可以很方便的把最终处理过后的数据拿到可以进行其他的拦截或操作。


代码比较简单都已经在文中贴出。后期我也会持续更新一些其他的设计模式在 Flutter 项目中的应用,还有一些真实开发场景中一些坑点与踩坑经历,大家可以关注一下。


我在 Flutter 应用方面还是一个新手来着,如果本文的讲解有什么错漏的地方,希望同学们一定要指出哦。有疑问也可以评论区交流学习进步,感谢!
当然如果觉得本文还不错对你有些帮助的话,还请点赞支持一下哦,你的支持是我最大的动力啦!
Ok,这一期就此完结。

用户评论