connection_button.dart 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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.disconnecting:
  135. gradientColor = [
  136. Get.reactiveTheme.highlightColor,
  137. Get.reactiveTheme.hintColor,
  138. ];
  139. imgPath = Assets.switchStatusDisconnected;
  140. statusImgPath = Assets.disconnected;
  141. text = "Disconnecting";
  142. textColor = Get.reactiveTheme.hintColor;
  143. break;
  144. case ConnectionState.error:
  145. gradientColor = [
  146. Get.reactiveTheme.highlightColor,
  147. Get.reactiveTheme.hintColor,
  148. ];
  149. imgPath = Assets.switchStatusDisconnected;
  150. statusImgPath = Assets.error;
  151. text = "Error";
  152. textColor = Get.reactiveTheme.hintColor;
  153. break;
  154. }
  155. // 保存前一个渐变色用于动画
  156. final previousColor = _previousGradientColor ?? gradientColor;
  157. // 只在颜色真正改变时更新
  158. if (_previousGradientColor == null ||
  159. _previousGradientColor!.length != gradientColor.length ||
  160. !_colorsEqual(_previousGradientColor!, gradientColor)) {
  161. // 延迟更新,避免在 build 中调用 setState
  162. WidgetsBinding.instance.addPostFrameCallback((_) {
  163. if (mounted) {
  164. _previousGradientColor = gradientColor;
  165. }
  166. });
  167. }
  168. return Column(
  169. children: [
  170. TweenAnimationBuilder<List<Color>>(
  171. duration: const Duration(milliseconds: 300),
  172. curve: Curves.easeInOut,
  173. tween: ColorListTween(begin: previousColor, end: gradientColor),
  174. builder: (context, colors, child) {
  175. return Container(
  176. width: 72.w,
  177. height: 132.w,
  178. padding: EdgeInsets.all(6.w),
  179. decoration: BoxDecoration(
  180. borderRadius: BorderRadius.circular(20.r),
  181. gradient: LinearGradient(
  182. begin: Alignment.topCenter,
  183. end: Alignment.bottomCenter,
  184. colors: colors,
  185. ),
  186. ),
  187. child: child,
  188. );
  189. },
  190. child: AnimatedAlign(
  191. duration: const Duration(milliseconds: 300),
  192. curve: Curves.fastOutSlowIn,
  193. alignment: _isAtTop ? Alignment.topCenter : Alignment.bottomCenter,
  194. child: Container(
  195. width: 60.w,
  196. height: 60.w,
  197. alignment: Alignment.center,
  198. decoration: BoxDecoration(
  199. color: Colors.white,
  200. borderRadius: BorderRadius.circular(14.r),
  201. ),
  202. child: AnimatedSwitcher(
  203. duration: const Duration(milliseconds: 300),
  204. transitionBuilder: (Widget child, Animation<double> animation) {
  205. return FadeTransition(
  206. opacity: animation,
  207. child: ScaleTransition(
  208. scale: Tween<double>(
  209. begin: 0.85,
  210. end: 1.0,
  211. ).animate(animation),
  212. child: child,
  213. ),
  214. );
  215. },
  216. child: _buildImageWithAnimation(imgPath),
  217. ),
  218. ),
  219. ),
  220. ),
  221. 20.verticalSpaceFromWidth,
  222. Row(
  223. mainAxisAlignment: MainAxisAlignment.center,
  224. children: [
  225. IXImage(
  226. source: statusImgPath,
  227. sourceType: ImageSourceType.asset,
  228. width: 14.w,
  229. height: 14.w,
  230. ),
  231. 4.horizontalSpace,
  232. Text(
  233. text,
  234. style: TextStyle(
  235. fontSize: 14.sp,
  236. fontWeight: FontWeight.w500,
  237. color: textColor,
  238. ),
  239. ),
  240. ],
  241. ),
  242. ],
  243. );
  244. }
  245. }
  246. // 自定义颜色列表插值器,用于渐变色动画
  247. class ColorListTween extends Tween<List<Color>> {
  248. ColorListTween({super.begin, super.end});
  249. @override
  250. List<Color> lerp(double t) {
  251. if (begin == null || end == null) {
  252. return end ?? begin ?? [];
  253. }
  254. final length = math.max(begin!.length, end!.length);
  255. return List.generate(length, (index) {
  256. final beginColor = index < begin!.length ? begin![index] : begin!.last;
  257. final endColor = index < end!.length ? end![index] : end!.last;
  258. return Color.lerp(beginColor, endColor, t) ?? endColor;
  259. });
  260. }
  261. }