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 toastData = _BetterToastData(
    message: message,
    child: child,
    icon: icon,
    fontSize: fontSize,
    textColor: textColor,
    backgroundColor: backgroundColor,
    padding: padding,
    borderRadius: borderRadius,
    position: position,
    topOffset: topOffset,
    bottomOffset: bottomOffset,
    textAlign: textAlign,
    forbidClick: forbidClick,
    width: width,
    height: height,
  );

  if (isGlobalToast) {
    final toastDuration = duration ?? const Duration(milliseconds: 1000);
    final currentToastHandle = _currentToastHandle;
    if (currentToastHandle != null && currentToastHandle.isActive) {
      currentToastHandle.update(
        data: toastData,
        duration: toastDuration,
        onHide: onHide,
      );
      return;
    }

    late _BetterToastHandle toastHandle;
    toastHandle = _BetterToastHandle(
      overlay: overlay,
      data: toastData,
      fadeDuration: fadeDuration ?? const Duration(milliseconds: 250),
      duration: toastDuration,
      onHide: onHide,
      onRemove: () {
        if (_currentToastHandle == toastHandle) {
          _currentToastHandle = null;
        }
      },
    );
    _currentToastHandle = toastHandle;
    toastHandle.show();
    return;
  }

  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),
  );

  Widget buildToastContent() {
    return 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,
                          ),
                        ),
                    ],
                  ),
                ),
          ),
        ),
      ),
    );
  }

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

          // Toast内容
          IgnorePointer(
            ignoring: forbidClick != true,
            child: buildToastContent(),
          ),
        ],
      );
    },
  );

  var isRemoved = false;
  var isDisposed = false;
  Timer? autoHideTimer;
  late Future<bool> Function() hideThisToastImmediately;

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

    if (_currentToastHider == hideThisToastImmediately) {
      _currentToastHider = null;
    }

    if (withAnimation && !isDisposed) {
      try {
        if (animationController.status != AnimationStatus.dismissed) {
          await animationController.reverse();
        }
        if (forbidClick == true &&
            fadeController.status != AnimationStatus.dismissed) {
          await fadeController.reverse();
        }
      } on TickerCanceled {
        // Toast may be replaced while its exit animation is running.
      }
    }

    if (overlayEntry.mounted) {
      overlayEntry.remove();
    }
    if (!isDisposed) {
      animationController.dispose();
      fadeController.dispose();
      isDisposed = true;
    }

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

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

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

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

  void scheduleAutoHide() {
    if (isRemoved || isDisposed) {
      return;
    }
    autoHideTimer = Timer(duration ?? const Duration(milliseconds: 1000), () {
      removeToast();
    });
  }

  // 启动动画(先显示遮罩层)
  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 previousHider = isGlobalToastWhenShown ? _currentToastHider : null;

    if (isGlobalToastWhenShown) {
      _currentToastHider = hideThisToastImmediately;
    }

    // 插入到Overlay
    overlay.insert(overlayEntry);
    final skipAnimation = await previousHider?.call() ?? false;

    if (isRemoved || isDisposed) {
      return;
    }

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

    scheduleAutoHide();
  }

  showToast();
}