gradient_circle_header.dart 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. import 'dart:async';
  2. import 'package:flutter/material.dart'
  3. hide RefreshIndicatorState, RefreshIndicator;
  4. import 'package:flutter_svg/flutter_svg.dart';
  5. import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
  6. import '../constants/assets.dart';
  7. /// 自定义渐变圆形到圆柱形刷新头部
  8. class GradientCircleHeader extends RefreshIndicator {
  9. /// refreshing content
  10. final Widget? refresh;
  11. /// complete content
  12. final Widget? complete;
  13. /// failed content
  14. final Widget? failed;
  15. /// idle Icon center in circle
  16. final Widget idleIcon;
  17. GradientCircleHeader({
  18. super.key,
  19. this.refresh,
  20. this.complete,
  21. super.completeDuration = const Duration(milliseconds: 600),
  22. this.failed,
  23. Widget? idleIcon,
  24. }) : idleIcon =
  25. idleIcon ??
  26. SvgPicture.asset(Assets.arrowDownCircle, width: 20, height: 20),
  27. super(height: 60.0, refreshStyle: RefreshStyle.UnFollow);
  28. @override
  29. State<StatefulWidget> createState() {
  30. return _GradientCircleHeaderState();
  31. }
  32. }
  33. class _GradientCircleHeaderState
  34. extends RefreshIndicatorState<GradientCircleHeader>
  35. with TickerProviderStateMixin {
  36. AnimationController? _animationController;
  37. late AnimationController _dismissCtl;
  38. late AnimationController _rotateCtl; // 旋转动画控制器
  39. late AnimationController _colorCtl; // 颜色过渡动画控制器
  40. double _currentOffset = 0.0; // 跟踪当前下拉距离
  41. // 默认状态的背景渐变
  42. final Color _defaultBgStartColor = const Color(0xA6BABABA);
  43. final Color _defaultBgEndColor = const Color(0x33404040);
  44. // 默认状态的边框渐变
  45. final List<Color> _defaultBorderColors = const [
  46. Color(0xFFDFDFDF),
  47. Color(0xFFA2A2A2),
  48. Color(0x4F6B6B6B),
  49. ];
  50. // 当前状态的背景渐变颜色
  51. Color _currentBgStartColor = const Color(0xA6BABABA);
  52. Color _currentBgEndColor = const Color(0x33404040);
  53. // 当前状态的边框渐变颜色
  54. List<Color> _currentBorderColors = [
  55. const Color(0xFFDFDFDF),
  56. const Color(0xFFA2A2A2),
  57. const Color(0x4F6B6B6B),
  58. ];
  59. // 成功状态的背景渐变
  60. final Color _successBgStartColor = const Color(0xFF0D2116);
  61. final Color _successBgEndColor = const Color(0xFF228643);
  62. // 成功状态的边框渐变
  63. final List<Color> _successBorderColors = [
  64. const Color(0xFF253B2E),
  65. const Color(0xFF41905A),
  66. const Color(0xFF7BF4AB),
  67. ];
  68. // 失败状态的背景渐变
  69. final Color _failedBgStartColor = const Color(0xFF1E1215);
  70. final Color _failedBgEndColor = const Color(0xFFAA4140);
  71. // 失败状态的边框渐变
  72. final List<Color> _failedBorderColors = [
  73. const Color(0xFF4B3033),
  74. const Color(0xFFDC7A7F),
  75. const Color(0xFFFFBDBB),
  76. ];
  77. @override
  78. void onOffsetChange(double offset) {
  79. // 圆形完全显示需要的距离:topMargin(12) + diameter(30) = 42
  80. const double circleFullyVisibleOffset = 42.0;
  81. // 更新当前下拉距离(但在特定状态下固定为完整圆形的距离)
  82. if (mounted) {
  83. setState(() {
  84. // 如果正在刷新、成功、失败状态,固定 offset 为完整圆形的距离
  85. if (mode == RefreshStatus.refreshing ||
  86. mode == RefreshStatus.completed ||
  87. mode == RefreshStatus.failed) {
  88. _currentOffset = circleFullyVisibleOffset;
  89. } else {
  90. _currentOffset = offset;
  91. }
  92. });
  93. }
  94. // 如果正在刷新、成功、失败状态,保持圆形状态但不强制设置值
  95. // 让 readyToRefresh 的动画自然完成
  96. if (mode == RefreshStatus.refreshing ||
  97. mode == RefreshStatus.completed ||
  98. mode == RefreshStatus.failed) {
  99. // 不在这里强制设置值,避免打断动画造成闪烁
  100. return;
  101. }
  102. // 如果动画正在执行(收缩动画),不打断它
  103. if (_animationController!.isAnimating) {
  104. return;
  105. }
  106. // 跟随 offset 变化
  107. if (offset <= circleFullyVisibleOffset) {
  108. // 第一阶段:圆形逐渐显示,不变形
  109. _animationController!.value = 0.0;
  110. } else {
  111. // 第二阶段:圆形完全显示后,继续下拉变成圆柱
  112. final double cylinderOffset = offset - circleFullyVisibleOffset;
  113. _animationController!.value = cylinderOffset.clamp(0.0, 50.0);
  114. }
  115. }
  116. @override
  117. Future<void> readyToRefresh() {
  118. // 使用短时间的快速动画收缩到圆形
  119. return _animationController!.animateTo(
  120. 0.0,
  121. duration: Duration(milliseconds: 150), // 快速收缩
  122. curve: Curves.easeOut,
  123. );
  124. }
  125. @override
  126. void initState() {
  127. _dismissCtl = AnimationController(
  128. vsync: this,
  129. duration: Duration(milliseconds: 400),
  130. value: 1.0,
  131. );
  132. _animationController = AnimationController(
  133. vsync: this,
  134. lowerBound: 0.0,
  135. upperBound: 50.0,
  136. duration: Duration(milliseconds: 150), // 缩短动画时长以匹配快速回弹
  137. );
  138. _rotateCtl = AnimationController(
  139. vsync: this,
  140. duration: Duration(milliseconds: 1000),
  141. );
  142. _colorCtl = AnimationController(
  143. vsync: this,
  144. duration: Duration(milliseconds: 300),
  145. );
  146. super.initState();
  147. }
  148. // 颜色过渡方法
  149. void _animateToColor({
  150. required Color bgStartColor,
  151. required Color bgEndColor,
  152. required List<Color> borderColors,
  153. }) {
  154. // 如果已经是目标颜色,不需要动画
  155. if (_currentBgStartColor == bgStartColor &&
  156. _currentBgEndColor == bgEndColor) {
  157. return;
  158. }
  159. final ColorTween bgStartTween = ColorTween(
  160. begin: _currentBgStartColor,
  161. end: bgStartColor,
  162. );
  163. final ColorTween bgEndTween = ColorTween(
  164. begin: _currentBgEndColor,
  165. end: bgEndColor,
  166. );
  167. void listener() {
  168. if (mounted) {
  169. setState(() {
  170. _currentBgStartColor = bgStartTween.evaluate(_colorCtl)!;
  171. _currentBgEndColor = bgEndTween.evaluate(_colorCtl)!;
  172. _currentBorderColors = borderColors; // 直接设置边框颜色
  173. });
  174. }
  175. }
  176. _colorCtl.removeListener(listener);
  177. _colorCtl.addListener(listener);
  178. _colorCtl.reset();
  179. _colorCtl.forward().then((_) {
  180. _colorCtl.removeListener(listener);
  181. });
  182. }
  183. @override
  184. bool needReverseAll() {
  185. return false;
  186. }
  187. @override
  188. Widget buildContent(BuildContext context, RefreshStatus? mode) {
  189. Widget? child;
  190. if (mode == RefreshStatus.refreshing) {
  191. // 刷新状态:启动旋转动画
  192. if (!_rotateCtl.isAnimating) {
  193. _rotateCtl.repeat();
  194. }
  195. // 确保可见
  196. if (_dismissCtl.value != 1.0) {
  197. _dismissCtl.value = 1.0;
  198. }
  199. // 确保动画值是0(圆形状态),但不强制设置避免闪烁
  200. // 如果动画还在执行,让它完成
  201. if (!_animationController!.isAnimating &&
  202. _animationController!.value != 0.0) {
  203. _animationController!.value = 0.0;
  204. }
  205. // 和 idle 状态一样显示圆形边框,但图标是旋转的 refresh
  206. const double circleRadius = 15.0;
  207. const double topMargin = 12.0;
  208. final double topCircleCenterY = topMargin + circleRadius;
  209. final double cylinderHeight = _animationController?.value ?? 0.0;
  210. final double bottomCircleCenterY = topCircleCenterY + cylinderHeight;
  211. final double arrowTopPosition =
  212. bottomCircleCenterY - 10.0; // 10是图标高度的一半(20/2)
  213. return widget.refresh ??
  214. SizedBox(
  215. height: 60.0,
  216. child: Stack(
  217. clipBehavior: Clip.none,
  218. children: <Widget>[
  219. // 绘制圆形边框
  220. CustomPaint(
  221. painter: _GradientCircleToCylinderPainter(
  222. listener: _animationController,
  223. bgStartColor: _currentBgStartColor,
  224. bgEndColor: _currentBgEndColor,
  225. borderColors: _currentBorderColors,
  226. currentOffset: _currentOffset,
  227. isDefaultStyle: true, // 刷新状态使用默认样式渐变方向
  228. ),
  229. child: Container(height: 60.0),
  230. ),
  231. // 旋转的刷新图标
  232. Positioned(
  233. left: 0,
  234. right: 0,
  235. top: arrowTopPosition,
  236. child: RotationTransition(
  237. turns: _rotateCtl,
  238. child: SvgPicture.asset(
  239. Assets.refreshCircle,
  240. width: 20,
  241. height: 20,
  242. ),
  243. ),
  244. ),
  245. ],
  246. ),
  247. );
  248. } else if (mode == RefreshStatus.completed) {
  249. // 停止旋转动画,启动颜色过渡动画
  250. _rotateCtl.stop();
  251. _animateToColor(
  252. bgStartColor: _successBgStartColor,
  253. bgEndColor: _successBgEndColor,
  254. borderColors: _successBorderColors,
  255. );
  256. // 重置 dismiss 控制器,确保可见
  257. if (_dismissCtl.value != 1.0) {
  258. _dismissCtl.value = 1.0;
  259. }
  260. // 确保动画值是0(圆形状态)
  261. if (!_animationController!.isAnimating &&
  262. _animationController!.value != 0.0) {
  263. _animationController!.value = 0.0;
  264. }
  265. // 和刷新状态一样显示圆形边框,但颜色变绿,图标变成成功图标
  266. const double circleRadius = 15.0;
  267. const double topMargin = 12.0;
  268. final double topCircleCenterY = topMargin + circleRadius;
  269. final double cylinderHeight = _animationController?.value ?? 0.0;
  270. final double bottomCircleCenterY = topCircleCenterY + cylinderHeight;
  271. final double arrowTopPosition =
  272. bottomCircleCenterY - 10.0; // 10是图标高度的一半(20/2)
  273. return widget.complete ??
  274. FadeTransition(
  275. opacity: _dismissCtl,
  276. child: SizedBox(
  277. height: 60.0,
  278. child: Stack(
  279. clipBehavior: Clip.none,
  280. children: <Widget>[
  281. // 绘制圆形边框(绿色渐变)
  282. CustomPaint(
  283. painter: _GradientCircleToCylinderPainter(
  284. listener: _animationController,
  285. bgStartColor: _currentBgStartColor,
  286. bgEndColor: _currentBgEndColor,
  287. borderColors: _currentBorderColors,
  288. currentOffset: _currentOffset,
  289. ),
  290. child: Container(height: 60.0),
  291. ),
  292. // 成功图标
  293. Positioned(
  294. left: 0,
  295. right: 0,
  296. top: arrowTopPosition,
  297. child: SvgPicture.asset(
  298. Assets.successCircle,
  299. width: 20,
  300. height: 20,
  301. ),
  302. ),
  303. ],
  304. ),
  305. ),
  306. );
  307. } else if (mode == RefreshStatus.failed) {
  308. // 停止旋转动画,启动颜色过渡动画
  309. _rotateCtl.stop();
  310. _animateToColor(
  311. bgStartColor: _failedBgStartColor,
  312. bgEndColor: _failedBgEndColor,
  313. borderColors: _failedBorderColors,
  314. );
  315. // 重置 dismiss 控制器,确保可见
  316. if (_dismissCtl.value != 1.0) {
  317. _dismissCtl.value = 1.0;
  318. }
  319. // 确保动画值是0(圆形状态)
  320. if (!_animationController!.isAnimating &&
  321. _animationController!.value != 0.0) {
  322. _animationController!.value = 0.0;
  323. }
  324. // 和刷新状态一样显示圆形边框,但颜色变红,图标变成失败图标
  325. const double circleRadius = 15.0;
  326. const double topMargin = 12.0;
  327. final double topCircleCenterY = topMargin + circleRadius;
  328. final double cylinderHeight = _animationController?.value ?? 0.0;
  329. final double bottomCircleCenterY = topCircleCenterY + cylinderHeight;
  330. final double arrowTopPosition =
  331. bottomCircleCenterY - 10.0; // 10是图标高度的一半(20/2)
  332. return widget.failed ??
  333. FadeTransition(
  334. opacity: _dismissCtl,
  335. child: SizedBox(
  336. height: 60.0,
  337. child: Stack(
  338. clipBehavior: Clip.none,
  339. children: <Widget>[
  340. // 绘制圆形边框(红色渐变)
  341. CustomPaint(
  342. painter: _GradientCircleToCylinderPainter(
  343. listener: _animationController,
  344. bgStartColor: _currentBgStartColor,
  345. bgEndColor: _currentBgEndColor,
  346. borderColors: _currentBorderColors,
  347. currentOffset: _currentOffset,
  348. ),
  349. child: Container(height: 60.0),
  350. ),
  351. // 失败图标
  352. Positioned(
  353. left: 0,
  354. right: 0,
  355. top: arrowTopPosition,
  356. child: SvgPicture.asset(
  357. Assets.failedCircle,
  358. width: 20,
  359. height: 20,
  360. ),
  361. ),
  362. ],
  363. ),
  364. ),
  365. );
  366. } else if (mode == RefreshStatus.idle || mode == RefreshStatus.canRefresh) {
  367. // 停止旋转动画,恢复原始颜色
  368. _rotateCtl.stop();
  369. _animateToColor(
  370. bgStartColor: _defaultBgStartColor,
  371. bgEndColor: _defaultBgEndColor,
  372. borderColors: _defaultBorderColors,
  373. );
  374. // 重置 dismiss 控制器,确保可见
  375. if (_dismissCtl.value != 1.0) {
  376. _dismissCtl.value = 1.0;
  377. }
  378. // 计算箭头位置 - 始终在底部圆(30x30)的中心
  379. const double circleRadius = 15.0;
  380. const double topMargin = 12.0;
  381. final double topCircleCenterY = topMargin + circleRadius; // 顶部圆心Y坐标 = 27
  382. // 圆柱高度
  383. final double cylinderHeight = _animationController?.value ?? 0.0;
  384. // 箭头在底部圆的中心:顶部圆心 + 圆柱延伸高度
  385. // 当cylinderHeight=0时,箭头在顶部圆中心
  386. // 当cylinderHeight>0时,箭头在底部圆中心(底部圆心 = 顶部圆心 + 圆柱高度)
  387. final double bottomCircleCenterY = topCircleCenterY + cylinderHeight;
  388. final double arrowTopPosition =
  389. bottomCircleCenterY - 10.0; // 10是图标高度的一半(20/2),让图标居中
  390. return FadeTransition(
  391. opacity: _dismissCtl,
  392. child: SizedBox(
  393. height: 60.0,
  394. child: Stack(
  395. clipBehavior: Clip.none,
  396. children: <Widget>[
  397. // 绘制圆形到圆柱形的渐变边框
  398. CustomPaint(
  399. painter: _GradientCircleToCylinderPainter(
  400. listener: _animationController,
  401. bgStartColor: _currentBgStartColor,
  402. bgEndColor: _currentBgEndColor,
  403. borderColors: _currentBorderColors,
  404. currentOffset: _currentOffset,
  405. isDefaultStyle: true, // idle/canRefresh 状态使用默认样式渐变方向
  406. ),
  407. child: Container(height: 60.0),
  408. ),
  409. // 箭头在底部圆(30x30)的中心
  410. Positioned(
  411. left: 0,
  412. right: 0,
  413. top: arrowTopPosition,
  414. child: widget.idleIcon,
  415. ),
  416. ],
  417. ),
  418. ),
  419. );
  420. }
  421. return SizedBox(height: 60.0, child: Center(child: child));
  422. }
  423. @override
  424. void resetValue() {
  425. _animationController!.reset();
  426. _dismissCtl.value = 1.0;
  427. _currentOffset = 0.0;
  428. _rotateCtl.reset();
  429. _colorCtl.reset();
  430. // 重置为原始颜色
  431. _currentBgStartColor = _defaultBgStartColor;
  432. _currentBgEndColor = _defaultBgEndColor;
  433. _currentBorderColors = List.from(_defaultBorderColors);
  434. }
  435. @override
  436. void dispose() {
  437. _dismissCtl.dispose();
  438. _animationController!.dispose();
  439. _rotateCtl.dispose();
  440. _colorCtl.dispose();
  441. super.dispose();
  442. }
  443. }
  444. /// 绘制从圆形到圆柱形的渐变边框效果
  445. class _GradientCircleToCylinderPainter extends CustomPainter {
  446. final Animation<double>? listener;
  447. final Color bgStartColor;
  448. final Color bgEndColor;
  449. final List<Color> borderColors;
  450. final double currentOffset; // 当前下拉距离
  451. final bool isDefaultStyle; // 是否为默认/刷新状态的样式
  452. double get value => listener!.value;
  453. _GradientCircleToCylinderPainter({
  454. this.listener,
  455. required this.bgStartColor,
  456. required this.bgEndColor,
  457. required this.borderColors,
  458. required this.currentOffset,
  459. this.isDefaultStyle = false,
  460. }) : super(repaint: listener);
  461. @override
  462. void paint(Canvas canvas, Size size) {
  463. final double middleW = size.width / 2;
  464. final double circleRadius = 15.0; // 圆的半径(改为15)
  465. final double strokeWidth = 2.0; // 边框宽度
  466. final double topMargin = 12.0; // 顶部边距
  467. const double circleFullyVisibleOffset = 42.0; // 圆形完全显示需要的距离(12 + 30 = 42)
  468. // 圆心位置
  469. final double circleCenterY = topMargin + circleRadius;
  470. // 计算下拉的距离,控制圆柱的高度
  471. final double pullDistance = value;
  472. // 背景渐变色
  473. final Gradient backgroundGradient = LinearGradient(
  474. colors: [bgStartColor, bgEndColor],
  475. // 默认/刷新状态:从右到左,其他状态:从上到下
  476. begin: isDefaultStyle ? Alignment.centerRight : Alignment.topCenter,
  477. end: isDefaultStyle ? Alignment.centerLeft : Alignment.bottomCenter,
  478. );
  479. // 边框渐变色(支持多个颜色)
  480. final Gradient borderGradient = LinearGradient(
  481. colors: borderColors,
  482. // 默认/刷新状态:从下到上(反转),其他状态:从上到下
  483. begin: isDefaultStyle ? Alignment.bottomCenter : Alignment.topCenter,
  484. end: isDefaultStyle ? Alignment.topCenter : Alignment.bottomCenter,
  485. );
  486. // 阴影效果
  487. final Paint shadowPaint = Paint()
  488. ..color = Colors.black.withOpacity(0.1)
  489. ..maskFilter = MaskFilter.blur(BlurStyle.normal, 2);
  490. // 第一阶段:圆形逐渐显示(使用裁剪)
  491. if (currentOffset < circleFullyVisibleOffset) {
  492. canvas.save();
  493. // 裁剪显示区域,让圆形从顶部逐渐出现
  494. canvas.clipRect(Rect.fromLTWH(0, 0, size.width, currentOffset));
  495. // 绘制阴影
  496. canvas.drawCircle(
  497. Offset(middleW, circleCenterY + 1),
  498. circleRadius,
  499. shadowPaint,
  500. );
  501. // 绘制背景
  502. final Rect bgRect = Rect.fromCircle(
  503. center: Offset(middleW, circleCenterY),
  504. radius: circleRadius,
  505. );
  506. final Paint bgPaint = Paint()
  507. ..shader = backgroundGradient.createShader(bgRect)
  508. ..style = PaintingStyle.fill;
  509. canvas.drawCircle(Offset(middleW, circleCenterY), circleRadius, bgPaint);
  510. // 绘制边框(渐变)
  511. final Paint borderPaint = Paint()
  512. ..shader = borderGradient.createShader(bgRect)
  513. ..style = PaintingStyle.stroke
  514. ..strokeWidth = strokeWidth
  515. ..strokeCap = StrokeCap.round;
  516. canvas.drawCircle(
  517. Offset(middleW, circleCenterY),
  518. circleRadius,
  519. borderPaint,
  520. );
  521. canvas.restore();
  522. } else {
  523. // 第二阶段:绘制圆柱形效果(包括 pullDistance = 0 的情况)
  524. if (pullDistance <= 0.1) {
  525. // pullDistance很小时,只绘制圆形
  526. // 绘制阴影
  527. canvas.drawCircle(
  528. Offset(middleW, circleCenterY + 1),
  529. circleRadius,
  530. shadowPaint,
  531. );
  532. // 绘制背景
  533. final Rect bgRect = Rect.fromCircle(
  534. center: Offset(middleW, circleCenterY),
  535. radius: circleRadius,
  536. );
  537. final Paint bgPaint = Paint()
  538. ..shader = backgroundGradient.createShader(bgRect)
  539. ..style = PaintingStyle.fill;
  540. canvas.drawCircle(
  541. Offset(middleW, circleCenterY),
  542. circleRadius,
  543. bgPaint,
  544. );
  545. // 绘制边框(渐变)
  546. final Paint borderPaint = Paint()
  547. ..shader = borderGradient.createShader(bgRect)
  548. ..style = PaintingStyle.stroke
  549. ..strokeWidth = strokeWidth
  550. ..strokeCap = StrokeCap.round;
  551. canvas.drawCircle(
  552. Offset(middleW, circleCenterY),
  553. circleRadius,
  554. borderPaint,
  555. );
  556. } else {
  557. // 绘制圆柱
  558. Path path = Path();
  559. // 绘制顶部半圆(上半部分)
  560. path.addArc(
  561. Rect.fromCircle(
  562. center: Offset(middleW, circleCenterY),
  563. radius: circleRadius,
  564. ),
  565. -3.14159, // π (左侧)
  566. 3.14159, // π (到右侧)
  567. );
  568. // 右侧垂直线
  569. path.lineTo(middleW + circleRadius, circleCenterY + pullDistance);
  570. // 绘制底部半圆
  571. path.addArc(
  572. Rect.fromCircle(
  573. center: Offset(middleW, circleCenterY + pullDistance),
  574. radius: circleRadius,
  575. ),
  576. 0, // 0 (右侧)
  577. 3.14159, // π (到左侧)
  578. );
  579. // 左侧垂直线(回到起点)
  580. path.lineTo(middleW - circleRadius, circleCenterY);
  581. // 绘制阴影
  582. canvas.drawPath(path.shift(Offset(0, 1)), shadowPaint);
  583. // 绘制背景
  584. final Rect bgRect = Rect.fromLTWH(
  585. middleW - circleRadius,
  586. circleCenterY - circleRadius,
  587. circleRadius * 2,
  588. circleRadius * 2 + pullDistance,
  589. );
  590. final Paint bgPaint = Paint()
  591. ..shader = backgroundGradient.createShader(bgRect)
  592. ..style = PaintingStyle.fill;
  593. canvas.drawPath(path, bgPaint);
  594. // 绘制边框(渐变)
  595. final Paint borderPaint = Paint()
  596. ..shader = borderGradient.createShader(bgRect)
  597. ..style = PaintingStyle.stroke
  598. ..strokeWidth = strokeWidth
  599. ..strokeCap = StrokeCap.round;
  600. canvas.drawPath(path, borderPaint);
  601. }
  602. }
  603. }
  604. @override
  605. bool shouldRepaint(CustomPainter oldDelegate) {
  606. return true;
  607. }
  608. }