animated_progress_bar.dart 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import 'package:flutter/material.dart';
  2. import 'bubble/bubble_widget.dart';
  3. /// 支持动画过渡和渐变色的进度条
  4. // 使用示例
  5. // AnimatedGradientProgressBar(
  6. // targetValue: 0.75,
  7. // gradient: LinearGradient(colors: [Colors.orange, Colors.red]),
  8. // height: 10,
  9. // borderRadius: 5,
  10. // )
  11. class AnimatedGradientProgressBar extends StatefulWidget {
  12. // 目标进度值(0~1)
  13. final double targetValue;
  14. // 动画时长
  15. final Duration duration;
  16. // 渐变色
  17. final Gradient gradient;
  18. // 进度条高度
  19. final double height;
  20. // 圆角半径
  21. final double borderRadius;
  22. const AnimatedGradientProgressBar({
  23. super.key,
  24. required this.targetValue,
  25. required this.duration,
  26. required this.gradient,
  27. this.height = 10.0,
  28. this.borderRadius = 7.0,
  29. });
  30. @override
  31. AnimatedGradientProgressBarState createState() =>
  32. AnimatedGradientProgressBarState();
  33. }
  34. class AnimatedGradientProgressBarState
  35. extends State<AnimatedGradientProgressBar>
  36. with TickerProviderStateMixin {
  37. late AnimationController _controller;
  38. late Animation<double> _animation;
  39. @override
  40. void initState() {
  41. super.initState();
  42. _controller = AnimationController(duration: widget.duration, vsync: this);
  43. _animation = CurvedAnimation(parent: _controller, curve: Curves.easeInOut);
  44. _startAnimation();
  45. }
  46. @override
  47. void didUpdateWidget(AnimatedGradientProgressBar oldWidget) {
  48. super.didUpdateWidget(oldWidget);
  49. if (oldWidget.targetValue != widget.targetValue) {
  50. _resetAnimation();
  51. _startAnimation();
  52. }
  53. }
  54. @override
  55. void dispose() {
  56. // 移除监听
  57. _animation.removeListener(() {});
  58. _controller.dispose();
  59. super.dispose();
  60. }
  61. void _startAnimation() {
  62. print('进度条 => 初始动画值: ${_animation.value}'); // 0.0
  63. // 先移除旧监听
  64. _animation.removeListener(() {});
  65. // 创建新的 Tween 动画
  66. _animation = Tween<double>(
  67. begin: 0,
  68. end: widget.targetValue,
  69. ).animate(_animation);
  70. // 添加监听器并启动动画
  71. _animation.addListener(() => setState(() {}));
  72. _controller.forward();
  73. Future.delayed(widget.duration, () {
  74. print('进度条 => 最终动画值: ${_animation.value}');
  75. });
  76. }
  77. void _resetAnimation() => _controller.reset();
  78. @override
  79. Widget build(BuildContext context) {
  80. return LayoutBuilder(
  81. builder: (context, constraints) {
  82. // 进度条的实际宽度
  83. final progressBarWidth = constraints.maxWidth;
  84. return Stack(
  85. // 允许子组件溢出自己本身的大小,默认是裁切的
  86. clipBehavior: Clip.none,
  87. children: [
  88. // 百分比文本
  89. Positioned(
  90. // 左侧偏移量,跟随当前进度的末尾,公式:当前比例值 * 进度条的宽度 - 气泡宽度
  91. left: widget.targetValue * progressBarWidth - (widget.targetValue > 0.5 ? 32 : 24),
  92. // 进度气泡,位于进度条上方
  93. bottom: widget.height + 0.85,
  94. child: AnimatedBuilder(
  95. animation: _animation,
  96. builder: (context, _) {
  97. return BubbleWidget(
  98. // 箭头方向
  99. arrowDirection: AxisDirection.down,
  100. arrowOffset: 22,
  101. arrowLength: 8,
  102. arrowRadius: 2,
  103. arrowWidth: 5,
  104. padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 7),
  105. borderRadius: BorderRadius.circular(9),
  106. // 气泡的背景颜色,取渐变色的第2个颜色
  107. backgroundColor: widget.gradient.colors.last,
  108. contentBuilder: (context) {
  109. return Text(
  110. '${(_animation.value * 100).toStringAsFixed(0)}%',
  111. style: const TextStyle(fontSize: 10, color: Colors.white),
  112. );
  113. },
  114. );
  115. },
  116. ),
  117. ),
  118. // 进度条
  119. ClipRRect(
  120. borderRadius: BorderRadius.circular(widget.borderRadius),
  121. child: Container(
  122. width: double.infinity,
  123. height: widget.height,
  124. decoration: BoxDecoration(
  125. color: Colors.grey[200],
  126. borderRadius: BorderRadius.circular(widget.borderRadius),
  127. ),
  128. child: Stack(
  129. children: [
  130. AnimatedBuilder(
  131. animation: _animation,
  132. builder: (context, _) {
  133. return FractionallySizedBox(
  134. widthFactor: _animation.value,
  135. alignment: Alignment.centerLeft,
  136. child: Container(
  137. decoration: BoxDecoration(
  138. gradient: widget.gradient,
  139. borderRadius: BorderRadius.circular(
  140. widget.borderRadius,
  141. ),
  142. ),
  143. ),
  144. );
  145. },
  146. ),
  147. ],
  148. ),
  149. ),
  150. ),
  151. ],
  152. );
  153. }
  154. );
  155. }
  156. }