import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:get/get.dart'; import 'package:vbvs_app/common/color/AppGlobal.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; class TopSlideNotification extends StatefulWidget { final String text; double? fontSize = 26.rpx; Color? textColor; double? slideOffset = 200; final Duration duration; TopSlideNotification({ super.key, this.text = '操作成功!', this.fontSize, this.textColor, this.slideOffset, this.duration = const Duration(seconds: 2), }); static OverlayEntry? _currentEntry; // 单例 OverlayEntry static bool _isShowing = false; static void show( BuildContext context, { String text = '操作成功!', double fontSize = 16, Color? textColor, double slideOffset = 300.0, Duration duration = const Duration(seconds: 2), }) { _showInternal( context: context, text: text, fontSize: fontSize, textColor: textColor, slideOffset: slideOffset, duration: duration, ); } // Internal implementation used by both methods static void _showInternal({ BuildContext? context, required String text, required double fontSize, Color? textColor, required double slideOffset, required Duration duration, }) { // 如果已有弹窗,先移除 _removeCurrentEntry(); // 获取有效的context,优先使用传入的context,没有则使用navigatorKey的context final effectiveContext = context ?? AppGlobal.navigatorKey.currentContext; if (effectiveContext == null) { debugPrint('TopSlideNotification: No valid context available'); return; } final overlay = Overlay.of(effectiveContext); if (overlay == null) { debugPrint('TopSlideNotification: Could not find Overlay'); return; } final entry = OverlayEntry( builder: (_) => TopSlideNotification( text: text, fontSize: fontSize, textColor: textColor, slideOffset: slideOffset, duration: duration, ), ); _currentEntry = entry; _isShowing = true; overlay.insert(entry); // 自动移除 Future.delayed(duration + const Duration(milliseconds: 500), () { _removeCurrentEntry(); }); } static void _removeCurrentEntry() { if (_currentEntry != null && _isShowing) { _currentEntry!.remove(); _currentEntry = null; _isShowing = false; } } @override State createState() => _TopSlideNotificationState(); } class _TopSlideNotificationState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation; bool _isAnimating = false; // 标志位,控制是否正在动画中 @override void initState() { super.initState(); // 初始化 AnimationController _controller = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); // 动画初始化完成后调用 SchedulerBinding.instance.addPostFrameCallback((_) { _startAnimation(); // 调用动画启动方法 }); } @override void didChangeDependencies() { super.didChangeDependencies(); // 获取屏幕高度,用于计算动画的偏移值 final screenHeight = MediaQuery.of(context).size.height; final offsetValue = widget.slideOffset! / screenHeight; // 设置动画 _animation = Tween(begin: const Offset(0, -1), end: Offset(0, offsetValue)) .animate(CurvedAnimation( parent: _controller, curve: Curves.easeOut, reverseCurve: Curves.easeIn, )); } @override void dispose() { // 确保动画控制器在组件销毁时被释放 _controller.dispose(); super.dispose(); } Color get _textColor { return widget.textColor ?? Get.find().currentColor.sc2; } @override Widget build(BuildContext context) { return Positioned( top: 140.rpx, left: 0, right: 0, child: SlideTransition( position: _animation, child: Material( color: stringToColor("#000000").withOpacity(0.8), child: Padding( padding: EdgeInsets.symmetric(vertical: 20.rpx), child: Container( child: Text( widget.text, textAlign: TextAlign.center, style: TextStyle( fontSize: widget.fontSize, color: _textColor, ), ), ), ), ), ), ); } // 执行动画 Future _startAnimation() async { if (_isAnimating) return; // 如果正在动画中,则不执行新的动画 _isAnimating = true; // 标记动画开始 try { await _controller.forward(); await Future.delayed(widget.duration); // 只有在组件仍然挂载时才执行 reverse 动作 if (mounted) { await _controller.reverse(); } } finally { _isAnimating = false; // 动画完成后,标记动画结束 } } }