show static method

void show({
  1. BuildContext? context,
  2. String? message,
  3. Widget? child,
  4. Widget? icon,
  5. Duration? fadeDuration,
  6. Duration? duration,
  7. double? fontSize,
  8. Color? textColor,
  9. Color? backgroundColor,
  10. EdgeInsets? padding,
  11. BorderRadius? borderRadius,
  12. BetterToastPosition position = BetterToastPosition.bottom,
  13. double? topOffset,
  14. double? bottomOffset,
  15. TextAlign? textAlign = TextAlign.center,
  16. bool? forbidClick = false,
  17. double? width,
  18. double? height,
  19. VoidCallback? onHide,
})

Implementation

static void show({
  BuildContext? context,

  /// Toast 的消息
  String? message,

  /// Toast 的子组件
  Widget? child,

  /// Toast 的图标
  Widget? icon,

  /// 动画持续时间
  Duration? fadeDuration,

  /// Toast 的持续时间
  Duration? duration,

  /// Toast 的字体大小
  double? fontSize,
  Color? textColor,

  /// Toast 的背景颜色
  Color? backgroundColor,

  /// Toast 的内边距
  EdgeInsets? padding,

  /// Toast 的圆角半径
  BorderRadius? borderRadius,

  /// Toast 的位置
  BetterToastPosition position = BetterToastPosition.bottom,

  /// 顶部偏移量
  double? topOffset,

  /// 底部偏移量
  double? bottomOffset,

  /// 文本对齐方式
  TextAlign? textAlign = TextAlign.center,

  /// 是否禁止点击
  bool? forbidClick = false,

  /// Toast 的宽度
  double? width,

  /// Toast 的高度
  double? height,
  VoidCallback? onHide,
}) {
  final overlay = _getOverlay(context);
  if (overlay == null) {
    return;
  }
  final screenHeight = BetterScreenUtil.screenHeight;
  topOffset ??= screenHeight * 0.2;
  bottomOffset ??= screenHeight * 0.2;
  final isGlobalToastWhenShown = isGlobalToast;

  // 动画控制器
  final animationController = AnimationController(
    vsync: overlay,
    duration: fadeDuration ?? const Duration(milliseconds: 250),
  );

  // 遮罩层控制器(仅当 forbidClick=true 时使用)
  final fadeController = AnimationController(
    vsync: overlay,
    duration: const Duration(milliseconds: 150),
  );
  final fadeAnimation = CurvedAnimation(
    parent: fadeController,
    curve: Curves.easeInOut,
  );

  // 位移动画
  final offsetAnimation =
      Tween<Offset>(
        begin: position == BetterToastPosition.center
            ? Offset.zero
            : Offset(0, position == BetterToastPosition.bottom ? 0.1 : -0.1),
        end: Offset.zero,
      ).animate(
        CurvedAnimation(
          parent: animationController,
          curve: Curves.easeOutQuad,
        ),
      );

  // 透明度动画
  final toastOpacityAnimation = Tween<double>(begin: 0, end: 1).animate(
    CurvedAnimation(parent: animationController, curve: Curves.easeIn),
  );

  // 创建OverlayEntry
  final overlayEntry = OverlayEntry(
    builder: (context) {
      return Stack(
        children: [
          // 禁止点击的遮罩层(条件渲染)
          if (forbidClick == true)
            Positioned.fill(
              child: FadeTransition(
                opacity: fadeAnimation,
                child: Container(color: Colors.transparent),
              ),
            ),

          // Toast内容
          Align(
            alignment: _getAlignment(position),
            child: SlideTransition(
              position: offsetAnimation,
              child: FadeTransition(
                opacity: toastOpacityAnimation,
                child: Material(
                  color: Colors.transparent,
                  child:
                      child ??
                      Container(
                        width: width,
                        height: height,
                        margin: EdgeInsets.only(
                          top: position == BetterToastPosition.top
                              ? (topOffset ?? 0)
                              : 0,
                          bottom: position == BetterToastPosition.bottom
                              ? (bottomOffset ?? 0)
                              : 0,
                        ),
                        constraints: BoxConstraints(
                          maxWidth:
                              width ?? BetterScreenUtil.screenWidth * 0.8,
                        ),
                        padding:
                            padding ??
                            EdgeInsets.symmetric(
                              horizontal: 12.bw,
                              vertical: 8.bw,
                            ),
                        decoration: BoxDecoration(
                          color:
                              backgroundColor ?? Colors.black.withAlpha(178),
                          borderRadius:
                              borderRadius ?? BorderRadius.circular(8.bw),
                        ),
                        child: Column(
                          mainAxisSize: MainAxisSize.min,
                          mainAxisAlignment: MainAxisAlignment.center,
                          crossAxisAlignment: CrossAxisAlignment.center,
                          children: [
                            if (icon != null) icon,
                            if (icon != null && message != null)
                              SizedBox(height: 8.bw),
                            if (message != null)
                              Text(
                                message,
                                textAlign: textAlign,
                                style: TextStyle(
                                  color: textColor ?? Colors.white,
                                  fontSize: fontSize ?? 14.bsp,
                                ),
                              ),
                          ],
                        ),
                      ),
                ),
              ),
            ),
          ),
        ],
      );
    },
  );

  var isRemoved = false;
  late Future<bool> Function() hideThisToastImmediately;

  Future<void> removeToast({
    bool withAnimation = true,
    bool triggerOnHide = true,
  }) async {
    if (isRemoved) {
      return;
    }
    isRemoved = true;

    if (_currentToastEntry == overlayEntry) {
      _currentToastEntry = null;
    }
    if (_currentToastAnimationController == animationController) {
      _currentToastAnimationController = null;
    }
    if (_currentToastFadeController == fadeController) {
      _currentToastFadeController = null;
    }
    if (_currentToastHider == hideThisToastImmediately) {
      _currentToastHider = null;
    }
    _activeToastHiders.remove(hideThisToastImmediately);

    if (withAnimation) {
      if (animationController.status != AnimationStatus.dismissed) {
        await animationController.reverse();
      }
      if (forbidClick == true &&
          fadeController.status != AnimationStatus.dismissed) {
        await fadeController.reverse();
      }
    }

    if (overlayEntry.mounted) {
      overlayEntry.remove();
    }
    animationController.dispose();
    fadeController.dispose();

    if (triggerOnHide) {
      onHide?.call();
    }
  }

  hideThisToastImmediately = () async {
    if (isRemoved) {
      return false;
    }

    final isShowing =
        animationController.status == AnimationStatus.forward ||
        animationController.status == AnimationStatus.completed;

    await removeToast(withAnimation: !isShowing, triggerOnHide: false);
    return isShowing;
  };

  // 启动动画(先显示遮罩层)
  Future<void> startAnimations() async {
    try {
      if (forbidClick == true) {
        await fadeController.forward();
      }
      await animationController.forward();
    } on TickerCanceled {
      // Toast may be replaced before its entrance animation completes.
    }
  }

  Future<void> showToast() async {
    final previousHiders = isGlobalToastWhenShown
        ? List<Future<bool> Function()>.of(_activeToastHiders)
        : const <Future<bool> Function()>[];

    _activeToastHiders.add(hideThisToastImmediately);

    if (isGlobalToastWhenShown) {
      _currentToastEntry = overlayEntry;
      _currentToastAnimationController = animationController;
      _currentToastFadeController = fadeController;
      _currentToastHider = hideThisToastImmediately;
    }

    // 插入到Overlay
    overlay.insert(overlayEntry);
    var skipAnimation = false;
    for (final hider in previousHiders) {
      skipAnimation = await hider() || skipAnimation;
    }

    if (skipAnimation) {
      if (forbidClick == true) {
        fadeController.value = 1;
      }
      animationController.value = 1;
      return;
    }
    await startAnimations();
  }

  showToast();

  // 延迟隐藏
  Future.delayed(duration ?? const Duration(seconds: 1), () async {
    await removeToast();
  });
}