connection_button.dart 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import 'package:flutter/material.dart' hide ConnectionState;
  2. import 'package:flutter_screenutil/flutter_screenutil.dart';
  3. import 'package:flutter_svg/flutter_svg.dart';
  4. import 'dart:math' as math;
  5. import 'package:get/get.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. void _onTap() {
  21. // 切换位置
  22. setState(() {
  23. _isAtTop = !_isAtTop;
  24. });
  25. if (widget.onTap != null) {
  26. widget.onTap!();
  27. }
  28. }
  29. // 比较两个颜色列表是否相等
  30. bool _colorsEqual(List<Color> a, List<Color> b) {
  31. if (a.length != b.length) return false;
  32. for (int i = 0; i < a.length; i++) {
  33. if (a[i].value != b[i].value) return false;
  34. }
  35. return true;
  36. }
  37. @override
  38. Widget build(BuildContext context) {
  39. return GestureDetector(onTap: _onTap, child: _buildMainButton());
  40. }
  41. Widget _buildMainButton() {
  42. // 根据状态和主题获取颜色
  43. List<Color> gradientColor;
  44. String svgPath;
  45. String text;
  46. Color textColor;
  47. switch (widget.state) {
  48. case ConnectionState.disconnected:
  49. gradientColor = [
  50. Get.reactiveTheme.highlightColor,
  51. Get.reactiveTheme.hintColor,
  52. ];
  53. svgPath = Assets.switchStatusDisconnectedLight;
  54. text = "Disconnected";
  55. textColor = Get.reactiveTheme.hintColor;
  56. if (_isAtTop) {
  57. _isAtTop = false;
  58. }
  59. break;
  60. case ConnectionState.connecting:
  61. gradientColor = [
  62. Get.reactiveTheme.highlightColor,
  63. Get.reactiveTheme.hintColor,
  64. ];
  65. svgPath = Assets.switchStatusConnectingLight;
  66. text = "Connecting";
  67. textColor = Get.reactiveTheme.hintColor;
  68. break;
  69. case ConnectionState.connected:
  70. gradientColor = [
  71. Get.reactiveTheme.shadowColor,
  72. Get.reactiveTheme.primaryColor,
  73. ];
  74. svgPath = Assets.switchStatusConnectedLight;
  75. text = "Connected";
  76. textColor = Get.reactiveTheme.textTheme.bodyLarge!.color!;
  77. if (!_isAtTop) {
  78. _isAtTop = true;
  79. }
  80. break;
  81. case ConnectionState.disconnecting:
  82. gradientColor = [
  83. Get.reactiveTheme.highlightColor,
  84. Get.reactiveTheme.hintColor,
  85. ];
  86. svgPath = Assets.switchStatusConnectingLight;
  87. text = "Disconnecting";
  88. textColor = Get.reactiveTheme.hintColor;
  89. break;
  90. case ConnectionState.error:
  91. gradientColor = [
  92. Get.reactiveTheme.highlightColor,
  93. Get.reactiveTheme.hintColor,
  94. ];
  95. svgPath = Assets.switchStatusDisconnectedLight;
  96. text = "Error";
  97. textColor = Get.reactiveTheme.hintColor;
  98. break;
  99. }
  100. // 保存前一个渐变色用于动画
  101. final previousColor = _previousGradientColor ?? gradientColor;
  102. // 只在颜色真正改变时更新
  103. if (_previousGradientColor == null ||
  104. _previousGradientColor!.length != gradientColor.length ||
  105. !_colorsEqual(_previousGradientColor!, gradientColor)) {
  106. // 延迟更新,避免在 build 中调用 setState
  107. WidgetsBinding.instance.addPostFrameCallback((_) {
  108. if (mounted) {
  109. _previousGradientColor = gradientColor;
  110. }
  111. });
  112. }
  113. return Column(
  114. children: [
  115. TweenAnimationBuilder<List<Color>>(
  116. duration: const Duration(milliseconds: 300),
  117. curve: Curves.easeInOut,
  118. tween: ColorListTween(begin: previousColor, end: gradientColor),
  119. builder: (context, colors, child) {
  120. return Container(
  121. width: 72.w,
  122. height: 132.w,
  123. padding: EdgeInsets.all(6.w),
  124. decoration: BoxDecoration(
  125. borderRadius: BorderRadius.circular(20.r),
  126. gradient: LinearGradient(
  127. begin: Alignment.topCenter,
  128. end: Alignment.bottomCenter,
  129. colors: colors,
  130. ),
  131. ),
  132. child: child,
  133. );
  134. },
  135. child: AnimatedAlign(
  136. duration: const Duration(milliseconds: 300),
  137. curve: Curves.fastOutSlowIn,
  138. alignment: _isAtTop ? Alignment.topCenter : Alignment.bottomCenter,
  139. child: Container(
  140. width: 60.w,
  141. height: 60.w,
  142. alignment: Alignment.center,
  143. decoration: BoxDecoration(
  144. color: Colors.white,
  145. borderRadius: BorderRadius.circular(14.r),
  146. ),
  147. child: AnimatedSwitcher(
  148. duration: const Duration(milliseconds: 300),
  149. transitionBuilder: (Widget child, Animation<double> animation) {
  150. return FadeTransition(
  151. opacity: animation,
  152. child: ScaleTransition(
  153. scale: Tween<double>(
  154. begin: 0.85,
  155. end: 1.0,
  156. ).animate(animation),
  157. child: child,
  158. ),
  159. );
  160. },
  161. child: SvgPicture.asset(
  162. svgPath,
  163. key: ValueKey(svgPath),
  164. alignment: Alignment.center,
  165. width: 30.w,
  166. height: 30.w,
  167. ),
  168. ),
  169. ),
  170. ),
  171. ),
  172. 20.verticalSpaceFromWidth,
  173. Row(
  174. mainAxisAlignment: MainAxisAlignment.center,
  175. children: [
  176. SvgPicture.asset(
  177. Assets.switchStatusDisconnectedLight,
  178. width: 14.w,
  179. height: 14.w,
  180. ),
  181. 4.horizontalSpace,
  182. Text(
  183. text,
  184. style: TextStyle(
  185. fontSize: 14.sp,
  186. fontWeight: FontWeight.w500,
  187. color: textColor,
  188. ),
  189. ),
  190. ],
  191. ),
  192. ],
  193. );
  194. }
  195. }
  196. // 自定义颜色列表插值器,用于渐变色动画
  197. class ColorListTween extends Tween<List<Color>> {
  198. ColorListTween({super.begin, super.end});
  199. @override
  200. List<Color> lerp(double t) {
  201. if (begin == null || end == null) {
  202. return end ?? begin ?? [];
  203. }
  204. final length = math.max(begin!.length, end!.length);
  205. return List.generate(length, (index) {
  206. final beginColor = index < begin!.length ? begin![index] : begin!.last;
  207. final endColor = index < end!.length ? end![index] : end!.last;
  208. return Color.lerp(beginColor, endColor, t) ?? endColor;
  209. });
  210. }
  211. }