connection_button.dart 8.2 KB

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