connection_button.dart 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import 'package:flutter/material.dart' hide ConnectionState;
  2. import 'package:flutter_screenutil/flutter_screenutil.dart';
  3. import 'dart:math' as math;
  4. import 'package:get/get.dart';
  5. import 'package:nomo/app/widgets/ix_image.dart';
  6. import '../../../constants/assets.dart';
  7. import '../../../../config/theme/theme_extensions/theme_extension.dart';
  8. import '../../../constants/enums.dart';
  9. class ConnectionButton extends StatefulWidget {
  10. final ConnectionState state;
  11. final VoidCallback? onTap;
  12. const ConnectionButton({super.key, required this.state, this.onTap});
  13. @override
  14. State<ConnectionButton> createState() => _ConnectionButtonState();
  15. }
  16. class _ConnectionButtonState extends State<ConnectionButton>
  17. with TickerProviderStateMixin {
  18. bool _isAtTop = false; // 控制按钮位置,false=底部,true=顶部
  19. List<Color>? _previousGradientColor; // 保存前一个渐变色用于动画过渡
  20. late AnimationController _rotationController; // 旋转动画控制器
  21. @override
  22. void initState() {
  23. super.initState();
  24. // 初始化旋转动画控制器
  25. _rotationController = AnimationController(
  26. duration: const Duration(seconds: 2),
  27. vsync: this,
  28. );
  29. }
  30. @override
  31. void dispose() {
  32. _rotationController.dispose();
  33. super.dispose();
  34. }
  35. void _onTap() {
  36. // 切换位置
  37. setState(() {
  38. _isAtTop = !_isAtTop;
  39. });
  40. if (widget.onTap != null) {
  41. widget.onTap!();
  42. }
  43. }
  44. // 比较两个颜色列表是否相等
  45. bool _colorsEqual(List<Color> a, List<Color> b) {
  46. if (a.length != b.length) return false;
  47. for (int i = 0; i < a.length; i++) {
  48. if (a[i].value != b[i].value) return false;
  49. }
  50. return true;
  51. }
  52. Widget _buildImageWithAnimation(String imgPath) {
  53. // 如果是connecting状态,添加旋转动画
  54. if (widget.state == ConnectionState.connecting) {
  55. // 启动旋转动画
  56. if (!_rotationController.isAnimating) {
  57. _rotationController.repeat();
  58. }
  59. return AnimatedBuilder(
  60. animation: _rotationController,
  61. builder: (context, child) {
  62. return Transform.rotate(
  63. angle: _rotationController.value * 2 * math.pi,
  64. child: IXImage(
  65. source: imgPath,
  66. sourceType: ImageSourceType.asset,
  67. width: 30.w,
  68. height: 30.w,
  69. ),
  70. );
  71. },
  72. );
  73. } else {
  74. // 其他状态停止旋转动画
  75. if (_rotationController.isAnimating) {
  76. _rotationController.stop();
  77. }
  78. return IXImage(
  79. source: imgPath,
  80. sourceType: ImageSourceType.asset,
  81. width: 30.w,
  82. height: 30.w,
  83. );
  84. }
  85. }
  86. @override
  87. Widget build(BuildContext context) {
  88. return GestureDetector(onTap: _onTap, child: _buildMainButton());
  89. }
  90. Widget _buildMainButton() {
  91. // 根据状态和主题获取颜色
  92. List<Color> gradientColor;
  93. String imgPath;
  94. String statusImgPath;
  95. String text;
  96. Color textColor;
  97. switch (widget.state) {
  98. case ConnectionState.disconnected:
  99. gradientColor = [
  100. Get.reactiveTheme.highlightColor,
  101. Get.reactiveTheme.hintColor,
  102. ];
  103. imgPath = Assets.switchStatusDisconnected;
  104. statusImgPath = Assets.disconnected;
  105. text = "Disconnected";
  106. textColor = Get.reactiveTheme.hintColor;
  107. if (_isAtTop) {
  108. _isAtTop = false;
  109. }
  110. break;
  111. case ConnectionState.connecting:
  112. gradientColor = [
  113. Get.reactiveTheme.highlightColor,
  114. Get.reactiveTheme.hintColor,
  115. ];
  116. imgPath = Assets.switchStatusConnecting;
  117. statusImgPath = Assets.connecting;
  118. text = "Connecting";
  119. textColor = Get.reactiveTheme.hintColor;
  120. break;
  121. case ConnectionState.connected:
  122. gradientColor = [
  123. Get.reactiveTheme.shadowColor,
  124. Get.reactiveTheme.primaryColor,
  125. ];
  126. imgPath = Assets.switchStatusConnected;
  127. statusImgPath = Assets.connected;
  128. text = "Connected";
  129. textColor = Get.reactiveTheme.textTheme.bodyLarge!.color!;
  130. if (!_isAtTop) {
  131. _isAtTop = true;
  132. }
  133. break;
  134. case ConnectionState.error:
  135. gradientColor = [
  136. Get.reactiveTheme.highlightColor,
  137. Get.reactiveTheme.hintColor,
  138. ];
  139. imgPath = Assets.switchStatusDisconnected;
  140. statusImgPath = Assets.error;
  141. text = "Error";
  142. textColor = Get.reactiveTheme.hintColor;
  143. break;
  144. }
  145. // 保存前一个渐变色用于动画
  146. final previousColor = _previousGradientColor ?? gradientColor;
  147. // 只在颜色真正改变时更新
  148. if (_previousGradientColor == null ||
  149. _previousGradientColor!.length != gradientColor.length ||
  150. !_colorsEqual(_previousGradientColor!, gradientColor)) {
  151. // 延迟更新,避免在 build 中调用 setState
  152. WidgetsBinding.instance.addPostFrameCallback((_) {
  153. if (mounted) {
  154. _previousGradientColor = gradientColor;
  155. }
  156. });
  157. }
  158. return Column(
  159. children: [
  160. TweenAnimationBuilder<List<Color>>(
  161. duration: const Duration(milliseconds: 300),
  162. curve: Curves.easeInOut,
  163. tween: ColorListTween(begin: previousColor, end: gradientColor),
  164. builder: (context, colors, child) {
  165. return Container(
  166. width: 72.w,
  167. height: 132.w,
  168. padding: EdgeInsets.all(6.w),
  169. decoration: BoxDecoration(
  170. borderRadius: BorderRadius.circular(20.r),
  171. gradient: LinearGradient(
  172. begin: Alignment.topCenter,
  173. end: Alignment.bottomCenter,
  174. colors: colors,
  175. ),
  176. ),
  177. child: child,
  178. );
  179. },
  180. child: AnimatedAlign(
  181. duration: const Duration(milliseconds: 300),
  182. curve: Curves.fastOutSlowIn,
  183. alignment: _isAtTop ? Alignment.topCenter : Alignment.bottomCenter,
  184. child: Container(
  185. width: 60.w,
  186. height: 60.w,
  187. alignment: Alignment.center,
  188. decoration: BoxDecoration(
  189. color: Colors.white,
  190. borderRadius: BorderRadius.circular(14.r),
  191. ),
  192. child: AnimatedSwitcher(
  193. duration: const Duration(milliseconds: 300),
  194. transitionBuilder: (Widget child, Animation<double> animation) {
  195. return FadeTransition(
  196. opacity: animation,
  197. child: ScaleTransition(
  198. scale: Tween<double>(
  199. begin: 0.85,
  200. end: 1.0,
  201. ).animate(animation),
  202. child: child,
  203. ),
  204. );
  205. },
  206. child: _buildImageWithAnimation(imgPath),
  207. ),
  208. ),
  209. ),
  210. ),
  211. 20.verticalSpaceFromWidth,
  212. Row(
  213. mainAxisAlignment: MainAxisAlignment.center,
  214. children: [
  215. IXImage(
  216. source: statusImgPath,
  217. sourceType: ImageSourceType.asset,
  218. width: 14.w,
  219. height: 14.w,
  220. ),
  221. 4.horizontalSpace,
  222. Text(
  223. text,
  224. style: TextStyle(
  225. fontSize: 14.sp,
  226. fontWeight: FontWeight.w500,
  227. color: textColor,
  228. ),
  229. ),
  230. ],
  231. ),
  232. ],
  233. );
  234. }
  235. }
  236. // 自定义颜色列表插值器,用于渐变色动画
  237. class ColorListTween extends Tween<List<Color>> {
  238. ColorListTween({super.begin, super.end});
  239. @override
  240. List<Color> lerp(double t) {
  241. if (begin == null || end == null) {
  242. return end ?? begin ?? [];
  243. }
  244. final length = math.max(begin!.length, end!.length);
  245. return List.generate(length, (index) {
  246. final beginColor = index < begin!.length ? begin![index] : begin!.last;
  247. final endColor = index < end!.length ? end![index] : end!.last;
  248. return Color.lerp(beginColor, endColor, t) ?? endColor;
  249. });
  250. }
  251. }