import 'package:flutter/material.dart'; import 'bubble/bubble_widget.dart'; /// 支持动画过渡和渐变色的进度条 // 使用示例 // AnimatedGradientProgressBar( // targetValue: 0.75, // gradient: LinearGradient(colors: [Colors.orange, Colors.red]), // height: 10, // borderRadius: 5, // ) class AnimatedGradientProgressBar extends StatefulWidget { // 目标进度值(0~1) final double targetValue; // 动画时长 final Duration duration; // 渐变色 final Gradient gradient; // 进度条高度 final double height; // 圆角半径 final double borderRadius; const AnimatedGradientProgressBar({ super.key, required this.targetValue, required this.duration, required this.gradient, this.height = 10.0, this.borderRadius = 7.0, }); @override AnimatedGradientProgressBarState createState() => AnimatedGradientProgressBarState(); } class AnimatedGradientProgressBarState extends State with TickerProviderStateMixin { late AnimationController _controller; late Animation _animation; @override void initState() { super.initState(); _controller = AnimationController(duration: widget.duration, vsync: this); _animation = CurvedAnimation(parent: _controller, curve: Curves.easeInOut); _startAnimation(); } @override void didUpdateWidget(AnimatedGradientProgressBar oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.targetValue != widget.targetValue) { _resetAnimation(); _startAnimation(); } } @override void dispose() { // 移除监听 _animation.removeListener(() {}); _controller.dispose(); super.dispose(); } void _startAnimation() { print('进度条 => 初始动画值: ${_animation.value}'); // 0.0 // 先移除旧监听 _animation.removeListener(() {}); // 创建新的 Tween 动画 _animation = Tween( begin: 0, end: widget.targetValue, ).animate(_animation); // 添加监听器并启动动画 _animation.addListener(() => setState(() {})); _controller.forward(); Future.delayed(widget.duration, () { print('进度条 => 最终动画值: ${_animation.value}'); }); } void _resetAnimation() => _controller.reset(); @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { // 进度条的实际宽度 final progressBarWidth = constraints.maxWidth; // 计算气泡距离左边的偏移量 double leftOffset = 0; if (_animation.value == 0) { leftOffset = 0; } else { leftOffset = _animation.value * progressBarWidth - (widget.targetValue > 0.5 ? 32 : 24); } return Stack( // 允许子组件溢出自己本身的大小,默认是裁切的 clipBehavior: Clip.none, children: [ // 百分比文本 Positioned( // 左侧偏移量,气泡跟随在当前进度的末尾,公式:当前比例值 * 进度条的宽度 - 气泡宽度 left: leftOffset, // 进度气泡,位于进度条上方 bottom: widget.height + 0.85, child: AnimatedBuilder( animation: _animation, builder: (context, _) { return BubbleWidget( // 箭头方向 arrowDirection: AxisDirection.down, // 箭头距离左边的偏移量 arrowOffset: leftOffset == 0 ? 10 : 22, arrowLength: 8, arrowRadius: 2, arrowWidth: 5, padding: const EdgeInsets.symmetric( vertical: 2, horizontal: 7, ), borderRadius: BorderRadius.circular(9), // 气泡的背景颜色,取渐变色的第2个颜色 backgroundColor: widget.gradient.colors.last, contentBuilder: (context) { return Text( '${(_animation.value * 100).toStringAsFixed(0)}%', style: const TextStyle( fontSize: 10, color: Colors.white, ), ); }, ); }, ), ), // 进度条 ClipRRect( borderRadius: BorderRadius.circular(widget.borderRadius), child: Container( width: double.infinity, height: widget.height, decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(widget.borderRadius), ), child: Stack( children: [ AnimatedBuilder( animation: _animation, builder: (context, _) { return FractionallySizedBox( widthFactor: _animation.value, alignment: Alignment.centerLeft, child: Container( decoration: BoxDecoration( gradient: widget.gradient, borderRadius: BorderRadius.circular( widget.borderRadius, ), ), ), ); }, ), ], ), ), ), ], ); }, ); } }