|
|
@@ -0,0 +1,592 @@
|
|
|
+import 'package:flutter/material.dart' hide ConnectionState;
|
|
|
+import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
|
+import 'dart:math' as math;
|
|
|
+import 'dart:async';
|
|
|
+import 'package:get/get.dart';
|
|
|
+import 'package:nomo/app/widgets/ix_image.dart';
|
|
|
+
|
|
|
+import '../../../constants/assets.dart';
|
|
|
+import '../../../../config/theme/theme_extensions/theme_extension.dart';
|
|
|
+import '../../../../config/translations/strings_enum.dart';
|
|
|
+import '../../../constants/enums.dart';
|
|
|
+
|
|
|
+/// 连接中状态的流光边框画笔
|
|
|
+class ConnectingBorderPainter extends CustomPainter {
|
|
|
+ final double rotationAngle;
|
|
|
+ final Color primaryColor;
|
|
|
+ final Color secondaryColor;
|
|
|
+
|
|
|
+ ConnectingBorderPainter({
|
|
|
+ required this.rotationAngle,
|
|
|
+ required this.primaryColor,
|
|
|
+ required this.secondaryColor,
|
|
|
+ });
|
|
|
+
|
|
|
+ @override
|
|
|
+ void paint(Canvas canvas, Size size) {
|
|
|
+ final center = Offset(size.width / 2, size.height / 2);
|
|
|
+ final radius = size.width / 2 - 2;
|
|
|
+ const strokeWidth = 4.0;
|
|
|
+
|
|
|
+ final rect = Rect.fromCircle(center: center, radius: radius);
|
|
|
+
|
|
|
+ // 连接中状态:绘制平滑的流光尾巴效果
|
|
|
+ final gradient = SweepGradient(
|
|
|
+ startAngle: 0,
|
|
|
+ endAngle: 2 * math.pi,
|
|
|
+ colors: [
|
|
|
+ primaryColor.withValues(alpha: 0.0),
|
|
|
+ primaryColor.withValues(alpha: 0.05),
|
|
|
+ primaryColor.withValues(alpha: 0.2),
|
|
|
+ primaryColor.withValues(alpha: 0.5),
|
|
|
+ primaryColor.withValues(alpha: 0.8),
|
|
|
+ primaryColor,
|
|
|
+ secondaryColor,
|
|
|
+ secondaryColor.withValues(alpha: 0.8),
|
|
|
+ secondaryColor.withValues(alpha: 0.4),
|
|
|
+ secondaryColor.withValues(alpha: 0.1),
|
|
|
+ primaryColor.withValues(alpha: 0.0),
|
|
|
+ ],
|
|
|
+ stops: const [0.0, 0.1, 0.2, 0.35, 0.45, 0.5, 0.55, 0.65, 0.8, 0.9, 1.0],
|
|
|
+ transform: GradientRotation(rotationAngle - math.pi / 2),
|
|
|
+ );
|
|
|
+
|
|
|
+ final paint = Paint()
|
|
|
+ ..shader = gradient.createShader(rect)
|
|
|
+ ..style = PaintingStyle.stroke
|
|
|
+ ..strokeWidth = strokeWidth
|
|
|
+ ..strokeCap = StrokeCap.round
|
|
|
+ ..isAntiAlias = true;
|
|
|
+
|
|
|
+ canvas.drawCircle(center, radius, paint);
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ bool shouldRepaint(covariant ConnectingBorderPainter oldDelegate) {
|
|
|
+ return oldDelegate.rotationAngle != rotationAngle ||
|
|
|
+ oldDelegate.primaryColor != primaryColor ||
|
|
|
+ oldDelegate.secondaryColor != secondaryColor;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class ConnectionThemeButton extends StatefulWidget {
|
|
|
+ final ConnectionState state;
|
|
|
+ final VoidCallback? onTap;
|
|
|
+
|
|
|
+ const ConnectionThemeButton({super.key, required this.state, this.onTap});
|
|
|
+
|
|
|
+ @override
|
|
|
+ State<ConnectionThemeButton> createState() => _ConnectionThemeButtonState();
|
|
|
+}
|
|
|
+
|
|
|
+class _ConnectionThemeButtonState extends State<ConnectionThemeButton>
|
|
|
+ with TickerProviderStateMixin {
|
|
|
+ late AnimationController _rotationController; // 旋转动画控制器
|
|
|
+ late AnimationController _fadeController; // 淡入淡出控制器
|
|
|
+ Timer? _connectingTimer; // 连接中状态的计时器
|
|
|
+ int _connectingTextIndex = 0; // 当前显示的连接文本索引(0-4)
|
|
|
+ ConnectionState? _previousState; // 保存前一个连接状态
|
|
|
+ bool _isStoppingRotation = false; // 是否正在停止旋转
|
|
|
+
|
|
|
+ @override
|
|
|
+ void initState() {
|
|
|
+ super.initState();
|
|
|
+ // 初始化旋转动画控制器
|
|
|
+ _rotationController = AnimationController(
|
|
|
+ duration: const Duration(milliseconds: 500),
|
|
|
+ vsync: this,
|
|
|
+ );
|
|
|
+
|
|
|
+ // 初始化淡入淡出控制器
|
|
|
+ _fadeController = AnimationController(
|
|
|
+ duration: const Duration(milliseconds: 600),
|
|
|
+ vsync: this,
|
|
|
+ value: 1.0,
|
|
|
+ );
|
|
|
+
|
|
|
+ // 如果初始状态是 connectingVirtual/connecting 或 disconnecting,启动动画
|
|
|
+ if (widget.state == ConnectionState.connectingVirtual ||
|
|
|
+ widget.state == ConnectionState.connecting ||
|
|
|
+ widget.state == ConnectionState.disconnecting) {
|
|
|
+ _rotationController.repeat();
|
|
|
+ if (widget.state == ConnectionState.connectingVirtual ||
|
|
|
+ widget.state == ConnectionState.connecting) {
|
|
|
+ _startConnectingTimer();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ void didUpdateWidget(ConnectionThemeButton oldWidget) {
|
|
|
+ super.didUpdateWidget(oldWidget);
|
|
|
+ // 处理状态变化
|
|
|
+ if (oldWidget.state != widget.state) {
|
|
|
+ _handleStateChange(oldWidget.state);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void _handleStateChange(ConnectionState oldState) {
|
|
|
+ if (widget.state == ConnectionState.connectingVirtual ||
|
|
|
+ widget.state == ConnectionState.connecting ||
|
|
|
+ widget.state == ConnectionState.disconnecting) {
|
|
|
+ // 进入旋转状态时,确保立即开始旋转
|
|
|
+ _isStoppingRotation = false;
|
|
|
+ // 先停止任何正在进行的动画,然后立即重新开始
|
|
|
+ _rotationController.stop();
|
|
|
+ _rotationController.repeat();
|
|
|
+
|
|
|
+ if (widget.state == ConnectionState.connectingVirtual ||
|
|
|
+ widget.state == ConnectionState.connecting) {
|
|
|
+ if (_connectingTimer == null || !_connectingTimer!.isActive) {
|
|
|
+ _startConnectingTimer();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ _stopConnectingTimer();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 从连接中/断开中切换到其他状态时,平滑停止旋转
|
|
|
+ if (_rotationController.isAnimating && !_isStoppingRotation) {
|
|
|
+ _isStoppingRotation = true;
|
|
|
+ // 计算剩余角度,让动画平滑停止在顶部(0度位置)
|
|
|
+ final currentValue = _rotationController.value;
|
|
|
+ // 停止重复,然后平滑减速到完整的一圈
|
|
|
+ _rotationController.stop();
|
|
|
+ _rotationController
|
|
|
+ .animateTo(
|
|
|
+ 1.0,
|
|
|
+ duration: Duration(
|
|
|
+ milliseconds: ((1.0 - currentValue) * 400).toInt().clamp(
|
|
|
+ 100,
|
|
|
+ 400,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ curve: Curves.easeOutCubic,
|
|
|
+ )
|
|
|
+ .then((_) {
|
|
|
+ if (mounted) {
|
|
|
+ _rotationController.reset();
|
|
|
+ _isStoppingRotation = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ _stopConnectingTimer();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ void dispose() {
|
|
|
+ _rotationController.dispose();
|
|
|
+ _fadeController.dispose();
|
|
|
+ _connectingTimer?.cancel();
|
|
|
+ super.dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ void _onTap() {
|
|
|
+ if (widget.onTap != null) {
|
|
|
+ widget.onTap!();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 启动连接中状态的文本轮播计时器
|
|
|
+ void _startConnectingTimer() {
|
|
|
+ _connectingTimer?.cancel();
|
|
|
+ _connectingTextIndex = -1;
|
|
|
+ _connectingTimer = Timer.periodic(const Duration(seconds: 5), (timer) {
|
|
|
+ if (mounted) {
|
|
|
+ setState(() {
|
|
|
+ // 每秒切换到下一个文本,循环显示0-4
|
|
|
+ _connectingTextIndex = (_connectingTextIndex + 1) % 5;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 停止连接中状态的文本轮播计时器
|
|
|
+ void _stopConnectingTimer() {
|
|
|
+ _connectingTimer?.cancel();
|
|
|
+ _connectingTimer = null;
|
|
|
+ _connectingTextIndex = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据索引获取对应的连接文本
|
|
|
+ String _getConnectingText() {
|
|
|
+ switch (_connectingTextIndex) {
|
|
|
+ case 0:
|
|
|
+ return Strings.securingData.tr;
|
|
|
+ case 1:
|
|
|
+ return Strings.encryptingTraffic.tr;
|
|
|
+ case 2:
|
|
|
+ return Strings.protectingPrivacy.tr;
|
|
|
+ case 3:
|
|
|
+ return Strings.safeConnection.tr;
|
|
|
+ case 4:
|
|
|
+ return Strings.yourDataIsSafe.tr;
|
|
|
+ default:
|
|
|
+ return Strings.connecting.tr;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 连接中状态的流光边框颜色
|
|
|
+ static const Color _connectingPrimaryColor = Color(0xFF4FC3F7); // 浅蓝色
|
|
|
+ static const Color _connectingSecondaryColor = Color(0xFF7E57C2); // 紫色
|
|
|
+
|
|
|
+ // 断开中状态的流光边框颜色
|
|
|
+ static const Color _disconnectingPrimaryColor = Color(0xFFFF7043); // 橙色
|
|
|
+ static const Color _disconnectingSecondaryColor = Color(0xFFFFCA28); // 黄色
|
|
|
+
|
|
|
+ // 连接成功状态的渐变色
|
|
|
+ static const Color _connectedGradientStart = Color(0xFF0EA5E9);
|
|
|
+ static const Color _connectedGradientEnd = Color(0xFF3B82F6);
|
|
|
+
|
|
|
+ // 连接成功状态的阴影色
|
|
|
+ static const Color _connectedShadowColor = Color(
|
|
|
+ 0x660B84FE,
|
|
|
+ ); // rgba(11, 132, 254, 0.40)
|
|
|
+
|
|
|
+ // 断开状态的阴影色
|
|
|
+ static const Color _disconnectedShadowColor = Color(
|
|
|
+ 0x14000000,
|
|
|
+ ); // rgba(0, 0, 0, 0.08)
|
|
|
+
|
|
|
+ // 构建流光动画背景(连接中/断开中状态共用)
|
|
|
+ Widget _buildAnimatingBackground({
|
|
|
+ required double size,
|
|
|
+ required ValueKey key,
|
|
|
+ required Color primaryColor,
|
|
|
+ required Color secondaryColor,
|
|
|
+ }) {
|
|
|
+ return Container(
|
|
|
+ key: key,
|
|
|
+ width: size,
|
|
|
+ height: size,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: Get.reactiveTheme.highlightColor,
|
|
|
+ shape: BoxShape.circle,
|
|
|
+ ),
|
|
|
+ child: Stack(
|
|
|
+ alignment: Alignment.center,
|
|
|
+ children: [
|
|
|
+ // 流光边框
|
|
|
+ AnimatedBuilder(
|
|
|
+ animation: _rotationController,
|
|
|
+ builder: (context, child) {
|
|
|
+ return CustomPaint(
|
|
|
+ size: Size(size, size),
|
|
|
+ painter: ConnectingBorderPainter(
|
|
|
+ rotationAngle: _rotationController.value * 2 * math.pi,
|
|
|
+ primaryColor: primaryColor,
|
|
|
+ secondaryColor: secondaryColor,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ // 内层背景圆(遮住边框内侧)
|
|
|
+ Container(
|
|
|
+ width: size - 8.w,
|
|
|
+ height: size - 8.w,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: Get.reactiveTheme.highlightColor,
|
|
|
+ shape: BoxShape.circle,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建圆形按钮背景
|
|
|
+ Widget _buildButtonBackground(ConnectionState state, bool shouldRotate) {
|
|
|
+ final size = 170.w;
|
|
|
+
|
|
|
+ switch (state) {
|
|
|
+ case ConnectionState.disconnected:
|
|
|
+ case ConnectionState.error:
|
|
|
+ // 断开状态:highlightColor 背景 + 4.w dividerColor 边框 + 阴影
|
|
|
+ return Container(
|
|
|
+ key: ValueKey('bg_disconnected_$state'),
|
|
|
+ width: size,
|
|
|
+ height: size,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: Get.reactiveTheme.highlightColor,
|
|
|
+ shape: BoxShape.circle,
|
|
|
+ border: Border.all(
|
|
|
+ color: Get.reactiveTheme.dividerColor,
|
|
|
+ width: 4.w,
|
|
|
+ ),
|
|
|
+ boxShadow: [
|
|
|
+ BoxShadow(
|
|
|
+ color: _disconnectedShadowColor,
|
|
|
+ offset: const Offset(0, 8),
|
|
|
+ blurRadius: 24,
|
|
|
+ spreadRadius: 0,
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+
|
|
|
+ case ConnectionState.connectingVirtual:
|
|
|
+ case ConnectionState.connecting:
|
|
|
+ // 连接中状态:highlightColor 背景 + 蓝紫色流光边框
|
|
|
+ return _buildAnimatingBackground(
|
|
|
+ size: size,
|
|
|
+ key: const ValueKey('bg_connecting'),
|
|
|
+ primaryColor: _connectingPrimaryColor,
|
|
|
+ secondaryColor: _connectingSecondaryColor,
|
|
|
+ );
|
|
|
+
|
|
|
+ case ConnectionState.disconnecting:
|
|
|
+ // 断开中状态:highlightColor 背景 + 橙黄色流光边框
|
|
|
+ return _buildAnimatingBackground(
|
|
|
+ size: size,
|
|
|
+ key: const ValueKey('bg_disconnecting'),
|
|
|
+ primaryColor: _disconnectingPrimaryColor,
|
|
|
+ secondaryColor: _disconnectingSecondaryColor,
|
|
|
+ );
|
|
|
+
|
|
|
+ case ConnectionState.connected:
|
|
|
+ // 连接成功状态:渐变背景 + 蓝色阴影
|
|
|
+ return Container(
|
|
|
+ key: const ValueKey('bg_connected'),
|
|
|
+ width: size,
|
|
|
+ height: size,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ gradient: const LinearGradient(
|
|
|
+ begin: Alignment.topLeft,
|
|
|
+ end: Alignment.bottomRight,
|
|
|
+ colors: [_connectedGradientStart, _connectedGradientEnd],
|
|
|
+ ),
|
|
|
+ shape: BoxShape.circle,
|
|
|
+ boxShadow: [
|
|
|
+ BoxShadow(
|
|
|
+ color: _connectedShadowColor,
|
|
|
+ offset: const Offset(0, 8),
|
|
|
+ blurRadius: 20,
|
|
|
+ spreadRadius: 0,
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据状态和主题获取中心图片路径
|
|
|
+ String _getCenterImagePath(ConnectionState state) {
|
|
|
+ final isDark = Get.isDarkMode;
|
|
|
+ switch (state) {
|
|
|
+ case ConnectionState.disconnected:
|
|
|
+ case ConnectionState.error:
|
|
|
+ return isDark ? Assets.darkDisconnected : Assets.lightDisconnected;
|
|
|
+ case ConnectionState.connectingVirtual:
|
|
|
+ case ConnectionState.connecting:
|
|
|
+ case ConnectionState.disconnecting:
|
|
|
+ return isDark ? Assets.darkDisconnected : Assets.lightDisconnected;
|
|
|
+ case ConnectionState.connected:
|
|
|
+ // 连接成功时使用白色图标(因为背景是蓝色渐变)
|
|
|
+ return Assets.darkConnected;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取中心图片的 key(相同图片使用相同 key,避免不必要的动画)
|
|
|
+ String _getCenterImageKey(ConnectionState state) {
|
|
|
+ switch (state) {
|
|
|
+ case ConnectionState.disconnected:
|
|
|
+ case ConnectionState.error:
|
|
|
+ return 'center_disconnected_${Get.isDarkMode}';
|
|
|
+ case ConnectionState.connectingVirtual:
|
|
|
+ case ConnectionState.connecting:
|
|
|
+ case ConnectionState.disconnecting:
|
|
|
+ return 'center_connecting_${Get.isDarkMode}';
|
|
|
+ case ConnectionState.connected:
|
|
|
+ return 'center_connected';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建中心图片
|
|
|
+ Widget _buildCenterImage(ConnectionState state) {
|
|
|
+ final imagePath = _getCenterImagePath(state);
|
|
|
+ return IXImage(
|
|
|
+ key: ValueKey(_getCenterImageKey(state)),
|
|
|
+ source: imagePath,
|
|
|
+ sourceType: ImageSourceType.asset,
|
|
|
+ width: 40.w,
|
|
|
+ height: 40.w,
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ return GestureDetector(onTap: _onTap, child: _buildMainButton());
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildMainButton() {
|
|
|
+ // 根据状态获取对应的资源和样式
|
|
|
+ String statusImgPath;
|
|
|
+ String text;
|
|
|
+ Color textColor;
|
|
|
+ bool shouldRotate;
|
|
|
+
|
|
|
+ switch (widget.state) {
|
|
|
+ case ConnectionState.disconnected:
|
|
|
+ statusImgPath = Assets.disconnected;
|
|
|
+ text = Strings.disconnected.tr;
|
|
|
+ textColor = Get.reactiveTheme.hintColor;
|
|
|
+ shouldRotate = false;
|
|
|
+ break;
|
|
|
+ case ConnectionState.connectingVirtual:
|
|
|
+ case ConnectionState.connecting:
|
|
|
+ statusImgPath = Assets.connecting;
|
|
|
+ text = _getConnectingText(); // 使用轮播文本
|
|
|
+ textColor = Get.reactiveTheme.hintColor;
|
|
|
+ shouldRotate = true;
|
|
|
+ break;
|
|
|
+ case ConnectionState.disconnecting:
|
|
|
+ statusImgPath = Assets.connecting;
|
|
|
+ text = Strings.disconnecting.tr;
|
|
|
+ textColor = Get.reactiveTheme.hintColor;
|
|
|
+ shouldRotate = true;
|
|
|
+ break;
|
|
|
+ case ConnectionState.connected:
|
|
|
+ statusImgPath = Assets.connected;
|
|
|
+ text = Strings.connected.tr;
|
|
|
+ textColor = Get.reactiveTheme.textTheme.bodyLarge!.color!;
|
|
|
+ shouldRotate = false;
|
|
|
+ break;
|
|
|
+ case ConnectionState.error:
|
|
|
+ statusImgPath = Assets.error;
|
|
|
+ text = Strings.error.tr;
|
|
|
+ textColor = Get.reactiveTheme.hintColor;
|
|
|
+ shouldRotate = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新前一个状态
|
|
|
+ if (_previousState != widget.state) {
|
|
|
+ WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
|
+ if (mounted) {
|
|
|
+ _previousState = widget.state;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return Column(
|
|
|
+ mainAxisSize: MainAxisSize.min,
|
|
|
+ children: [
|
|
|
+ // 圆形按钮区域
|
|
|
+ SizedBox(
|
|
|
+ width: 170.w,
|
|
|
+ height: 170.w,
|
|
|
+ child: Stack(
|
|
|
+ alignment: Alignment.center,
|
|
|
+ children: [
|
|
|
+ // 按钮背景 - 使用纯淡入淡出
|
|
|
+ AnimatedSwitcher(
|
|
|
+ duration: const Duration(milliseconds: 600),
|
|
|
+ switchInCurve: Curves.easeInOut,
|
|
|
+ switchOutCurve: Curves.easeInOut,
|
|
|
+ transitionBuilder: (Widget child, Animation<double> animation) {
|
|
|
+ return FadeTransition(
|
|
|
+ opacity: CurvedAnimation(
|
|
|
+ parent: animation,
|
|
|
+ curve: Curves.easeInOut,
|
|
|
+ ),
|
|
|
+ child: child,
|
|
|
+ );
|
|
|
+ },
|
|
|
+ layoutBuilder: (currentChild, previousChildren) {
|
|
|
+ return Stack(
|
|
|
+ alignment: Alignment.center,
|
|
|
+ children: [
|
|
|
+ ...previousChildren,
|
|
|
+ if (currentChild != null) currentChild,
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ },
|
|
|
+ child: _buildButtonBackground(widget.state, shouldRotate),
|
|
|
+ ),
|
|
|
+ // 中心图片 - 纯淡入淡出
|
|
|
+ AnimatedSwitcher(
|
|
|
+ duration: const Duration(milliseconds: 500),
|
|
|
+ switchInCurve: Curves.easeInOut,
|
|
|
+ switchOutCurve: Curves.easeInOut,
|
|
|
+ transitionBuilder: (Widget child, Animation<double> animation) {
|
|
|
+ return FadeTransition(
|
|
|
+ opacity: CurvedAnimation(
|
|
|
+ parent: animation,
|
|
|
+ curve: Curves.easeInOut,
|
|
|
+ ),
|
|
|
+ child: child,
|
|
|
+ );
|
|
|
+ },
|
|
|
+ layoutBuilder: (currentChild, previousChildren) {
|
|
|
+ return Stack(
|
|
|
+ alignment: Alignment.center,
|
|
|
+ children: [
|
|
|
+ ...previousChildren,
|
|
|
+ if (currentChild != null) currentChild,
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ },
|
|
|
+ child: _buildCenterImage(widget.state),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ 20.verticalSpaceFromWidth,
|
|
|
+ // 状态文字
|
|
|
+ AnimatedSwitcher(
|
|
|
+ duration: const Duration(milliseconds: 350),
|
|
|
+ switchInCurve: Curves.easeOutCubic,
|
|
|
+ switchOutCurve: Curves.easeInCubic,
|
|
|
+ transitionBuilder: (Widget child, Animation<double> animation) {
|
|
|
+ return FadeTransition(
|
|
|
+ opacity: CurvedAnimation(
|
|
|
+ parent: animation,
|
|
|
+ curve: Curves.easeInOut,
|
|
|
+ ),
|
|
|
+ child: SlideTransition(
|
|
|
+ position:
|
|
|
+ Tween<Offset>(
|
|
|
+ begin: const Offset(0, 0.15),
|
|
|
+ end: Offset.zero,
|
|
|
+ ).animate(
|
|
|
+ CurvedAnimation(
|
|
|
+ parent: animation,
|
|
|
+ curve: Curves.easeOutCubic,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ child: child,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ child: SizedBox(
|
|
|
+ key: ValueKey('status_$text'), // 使用文本作为 key,确保文字改变时触发动画
|
|
|
+ height: 20.w,
|
|
|
+ child: Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
+ mainAxisSize: MainAxisSize.min,
|
|
|
+ children: [
|
|
|
+ IXImage(
|
|
|
+ source: statusImgPath,
|
|
|
+ sourceType: ImageSourceType.asset,
|
|
|
+ width: 14.w,
|
|
|
+ height: 14.w,
|
|
|
+ ),
|
|
|
+ 4.horizontalSpace,
|
|
|
+ Text(
|
|
|
+ text,
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 14.sp,
|
|
|
+ fontWeight: FontWeight.w500,
|
|
|
+ height: 1.4,
|
|
|
+ color: textColor,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|