关于 flutter 中自定义toast的总结

上周组内项目 安排在应用内显示一个toast的弹窗提醒 由于是flutter项目,其框架本身所提供的toast组件并不好用,UI上也不支持自定义的设计。因此决定自己写一个toast组件。虽然是一个很小的组件,但是前前后后也折腾了快两个礼拜,也算是正式上手flutter开发后的第一次实践吧。

1.0

由于toast出现的时间是不定的,因此它将以绝对定位的方式插入页面之中。所以使用了 overlay 这个类,该类的具体介绍这里就不展开叙述了。Overlay 主要就是两个方法,1. 往Overlay中插入entry,2. 删除Overlay中的entry
具体实现如下

static Future _show(BuildContext context, {@required String msg}) {
    OverlayEntry entry = new OverlayEntry(
        builder: (BuildContext context) => Positioned(
              //top值,可以改变这个值来改变toast在屏幕中的位置
              top: MediaQuery.of(context).size.height * 1 / 30,
              child: Container(
                  alignment: Alignment.center,
                  width: MediaQuery.of(context).size.width,
                  child: Padding(
                    padding: EdgeInsets.symmetric(horizontal: 10.0),
                    child: _buildToastWidget(context, msg),
                  )),
            ));

    ///往Overlay中插入插入OverlayEntry
    Overlay.of(context).insert(entry);

    ///两秒后,移除Toast
    Future result = Future.delayed(Duration(seconds: 1)).then((value) {
      entry.remove();
    });

    _lastToast = result;
    return result;
  }
//toast UI绘制
  static _buildToastWidget(context, String msg) {
    return Container(
      width: MediaQuery.of(context).size.width * 0.8,
      alignment: Alignment.center,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(12.0),
        shape: BoxShape.rectangle,
        color: Colors.white,
        border: Border.all(color: Colors.black, width: 2),
      ),
      child: Padding(
        padding: EdgeInsets.only(top: 10, bottom: 10),
        child: Text(msg, style: TextStyle(color: Colors.black, decoration: TextDecoration.none, fontSize: 20)),
      ),
    );
  }

这样实现的toast在使用时只需要传入context(上下文)便可以在页面上的指定位置插入一个我们所需要的toast了。

等等,是不是觉得功能还有些欠缺,点一次出来一个toast的用户体验貌似不太好。那加一个处理重复点击的函数吧

1.1

//处理重复多次点击
  static void _handleDuplicateAndShow(String message, int type, BuildContext context, int duration) {
    if (_lastMsg == message) {
      //相同信息内容
      int currentms = DateTime.now().millisecondsSinceEpoch;
      int interval = currentms - _lastShowms;
      if (interval > MIN_INTERVAL) {
        //大于时间间隔 可以显示
        _show(context, msg);
        _lastShowms = currentms;
      }
    } else {
      _show(context, msg);
      _lastMsg = message;
    }
  }

看起来仿佛是大功告成了,只需要向外暴露出相关的接口,便可以供开发者自由使用了。

1.2

持续封装

/// 提示
  static void showInfo(String message, {BuildContext context, int duration}) {
    //调用
  }

  /// 警告
  static void showWarning(String message, {BuildContext context, int duration}) {
  //
  }

  /// 成功
  static void showSuccess(String message, {BuildContext context, int duration}) {
    //
  }

  /// 错误
  static void showError(String message, {BuildContext context, int duration}) {
   //
  }

2.0

到这里了,相应的功能实现了,接口也对外暴露了。需求基本上实现了,但是代码的结构粗略看来还是很乱的。leader建议修改 ,使用MVVM来对这个toast进行改造。
那首先用toast这个类来复习一下MVVM的设计模式吧。
关于 flutter 中自定义toast的总结_第1张图片
由于需要对Toast的设计具有可扩展性,因此拆分出 ToastWidget 作为整个设计中的 View部分,使用ToastManager作为viewModel来管理整个toast的显示,toast这个类则是model层,负责用数据去实例化一个toast。
关于 flutter 中自定义toast的总结_第2张图片
建立目录结构如上。开始进行实现。

2.1

由于toast出现的场景、时间具有很强的不确定性,我们无法判断其所在的具体上下文。这里最开始考虑使用dart中的Stream流的机制,对toast事件创建一个事件流,并进行监听。

StreamController<Toast> _toastController = StreamController<Toast>();
 Stream<Toast> get toastStream => _toastController.stream;

但是流的机制存在的问题便是,我们无法对流中的事件进行缓存处理,即当业务需求为 一个个展示toast,当前toast动画执行未结束时不展示下一个,会发现很难做到这点。因此,最后的选择方案为 使用 Queue进行事件的保存与展示。

2.2

我们首先在Manager中定义一个队列

static Queue<Toast> toastQueue = Queue();

当队列中有任务在执行(即 有toast在展示时,下一个任务等待,在当前展示完成后的回调中去获取下一个展示的toast事件。)

static void _handleOneByOneShow(String message, int type, BuildContext context, int duration) {
    toastQueue.add(Toast(message, type, duration));
    if (isShowing) {
      //当前toast仍在显示
      return;
    } else {
      isShowing = true;
      //当前无toast显示 队列不为空 递归调用
      _recursiveShow(toastQueue, context);
    }
  }

  //递归的函数
  static void _recursiveShow(Queue toastQueue, BuildContext context) {
    if (toastQueue.isNotEmpty) {
      Toast firstToast = toastQueue.first;
      firstToast.show(context).whenComplete(() =>{
      toastQueue.removeFirst(), 
      _recursiveShow(toastQueue, context)
      }
      );
    } else {
      isShowing = false;
      return;
    }
  }

至此,我们的toast也就完成了基本的封装和优化了。又可以开心的玩耍了。
关于 flutter 中自定义toast的总结_第3张图片

你可能感兴趣的:(flutter)