Bootstrap

Flutter中Get.snackbar和Get.dialog关闭冲突问题记录

背景:
在使用GetX框架时,同时使用了Get.snackbar提示框和Get.dialog加载框,当这两个widget同时存在时,Get.dialog加载框调用Get.back()无法正常关闭。

冲突解释:
之所以会产生冲突,是因为Get.snackbar在关闭时会有一个动画,这个动画的默认持续时间为1s,这个动画的持续时间内,Get.snackbar并没有真正意义上的关闭,这时候我们调用Get.back()是无法关闭Get.dialog。

实现:
方案一

  1. 创建一个统一的SnackBarManager去管理提示框。
import 'package:get/get.dart';
import 'package:flutter/material.dart';

class SnackBarManager {
  factory SnackBarManager() => instance;

  static final SnackBarManager instance = SnackBarManager._internal();

  SnackBarManager._internal();


  String _lastMessage = '';

  int _lastDuration = 0;

  int _lastTime = 0;

  void showSnackBar(String title, String message, {Color? backgroundColor, Duration? duration}) {
    var currentTime = DateTime.now().millisecondsSinceEpoch;
    // 相同消息持续时间内重复提交时,返回
    if (currentTime - _lastTime < _lastDuration * 1000 && _lastMessage == message) {
      return;
    }

    dismissSnackBar();

    Get.snackbar(
      title,
      message,
      backgroundColor: backgroundColor ?? Colors.black12,
      duration: duration ?? Duration(seconds: 2), // 提示框持续时间
      animationDuration: Duration(milliseconds: 0), // 过渡动画的时间,这里设置为0是为了在使用Get.dialog时避免关闭冲突
    );

    _lastDuration = duration?.inSeconds ?? 2;
    _lastTime = DateTime.now().millisecondsSinceEpoch;
    _lastMessage = message;
  }

  void dismissSnackBar() {
    Get.closeCurrentSnackbar();
  }
}

这是一个避免重复显示的显示的提示框,可参考博客:Flutter中Get.snackbar避免重复显示的实现
2. 创建一个LoadingDialog去实现加载框。

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';

class LoadingDialog {
  static void show([String? msg]) {
    Get.dialog(
      PopScope(
        canPop: false,
        child: Center(
          child: Container(
            padding: EdgeInsets.symmetric(horizontal: 24, vertical: 16), // 减小内边距
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(8),
            ),
            child: Row(
              mainAxisSize: MainAxisSize.min, // 设置为最小宽度
              children: [
                SizedBox(
                  width: 24, // 减小加载图标尺寸
                  height: 24,
                  child: CircularProgressIndicator(
                    strokeWidth: 2.0, // 可以适当减小进度条宽度
                  ),
                ),
                SizedBox(width: 12), // 减小间距
                Text(
                  msg ?? "加载中...",
                  style: TextStyle(fontSize: 14, color: Colors.black), // 可以适当调整字体大小
                ),
              ],
            ),
          ),
        ),
      ),
      barrierDismissible: false,
    );
  }

  static void hide() {
    if (Get.isDialogOpen ?? false) {
      // 先关闭 Snackbar
      Get.closeCurrentSnackbar();
      // 添加延迟以确保 Snackbar 已关闭
      Future.delayed(Duration(milliseconds: 100), () {
        if (Get.isDialogOpen ?? false) {
          Get.back();
        }
      });
    }
  }
}

在关闭加载框之前,会先关闭提示框,由于Get.snackbar动画时长已经设置为0,此时延迟100ms后再关闭加载框是没有问题的。

方案二

  1. 通过源码可以看出,在调用Get.back()方法时,默认传参closeOverlays为false,closeOverlays参数表示是否关闭所有覆盖层,覆盖层包括对话框、Snackbar、底部弹出框等,isSnackbarOpen为是否存在Snakbar,默认状态下,存在Snackbar时,会执行closeCurrentSnackbar()并retrun。
  void back<T>({
    T? result,
    bool closeOverlays = false,
    bool canPop = true,
    int? id,
  }) {
    //TODO: This code brings compatibility of the new snackbar with GetX 4,
    // remove this code in version 5
    if (isSnackbarOpen && !closeOverlays) {
      closeCurrentSnackbar();
      return;
    }

    if (closeOverlays && isOverlaysOpen) {
      //TODO: This code brings compatibility of the new snackbar with GetX 4,
      // remove this code in version 5
      if (isSnackbarOpen) {
        closeAllSnackbars();
      }
      navigator?.popUntil((route) {
        return (!isDialogOpen! && !isBottomSheetOpen!);
      });
    }
    if (canPop) {
      if (global(id).currentState?.canPop() == true) {
        global(id).currentState?.pop<T>(result);
      }
    } else {
      global(id).currentState?.pop<T>(result);
    }
  }
  1. 知道原因后就简单了,我们只需将closeOverlays传参为true则可完美解决冲突问题。
    if (Get.isDialogOpen ?? false) {
      Get.back(closeOverlays: true);
    }
;