基于Flutter实现带指示点的百分比弧形Widget开发求助
实现渐变弧形百分比指示器Widget
核心思路
通过自定义CustomPainter绘制渐变弧形、指示点,结合Stack布局放置中间百分比文本。核心是根据传入的百分比计算指示点的坐标,并提取对应位置的渐变颜色,保证文字与指示点颜色匹配。
完整代码实现
import 'package:flutter/material.dart'; class GradientArcIndicator extends StatefulWidget { final double percentage; // 传入0-100之间的数值 final double strokeWidth; final Size size; const GradientArcIndicator({ super.key, required this.percentage, this.strokeWidth = 16, this.size = const Size(200, 200), }); @override State<GradientArcIndicator> createState() => _GradientArcIndicatorState(); } class _GradientArcIndicatorState extends State<GradientArcIndicator> { // 根据百分比获取对应位置的渐变颜色 Color getMatchColor() { final gradient = LinearGradient( colors: [Colors.blue, Colors.green, Colors.yellow, Colors.red], stops: const [0.0, 0.33, 0.66, 1.0], begin: Alignment.centerLeft, end: Alignment.centerRight, ); final fraction = widget.percentage / 100; return gradient.transform(Alignment(fraction * 2 - 1, 0)).color; } @override Widget build(BuildContext context) { final matchColor = getMatchColor(); return SizedBox.fromSize( size: widget.size, child: Stack( alignment: Alignment.center, children: [ // 背景灰色弧形 CustomPaint( painter: _BackgroundArcPainter( strokeWidth: widget.strokeWidth, color: Colors.grey.shade200, ), ), // 渐变前景弧形 CustomPaint( painter: _ForegroundArcPainter( percentage: widget.percentage, strokeWidth: widget.strokeWidth, gradient: LinearGradient( colors: [Colors.blue, Colors.green, Colors.yellow, Colors.red], stops: const [0.0, 0.33, 0.66, 1.0], ), ), ), // 指示点 CustomPaint( painter: _IndicatorDotPainter( percentage: widget.percentage, dotRadius: widget.strokeWidth / 2 + 4, color: matchColor, ), ), // 中间百分比文本 Text( '${widget.percentage.toInt()}%', style: TextStyle( fontSize: widget.size.width * 0.2, fontWeight: FontWeight.bold, color: matchColor, ), ), ], ), ); } } // 绘制背景弧形 class _BackgroundArcPainter extends CustomPainter { final double strokeWidth; final Color color; _BackgroundArcPainter({required this.strokeWidth, required this.color}); @override void paint(Canvas canvas, Size size) { final rect = Rect.fromCircle( center: Offset(size.width / 2, size.height / 2), radius: (size.width - strokeWidth) / 2, ); final paint = Paint() ..color = color ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.round; canvas.drawArc(rect, 0, 2 * 3.1415926, false, paint); } @override bool shouldRepaint(covariant _BackgroundArcPainter oldDelegate) { return oldDelegate.strokeWidth != strokeWidth || oldDelegate.color != color; } } // 绘制渐变前景弧形 class _ForegroundArcPainter extends CustomPainter { final double percentage; final double strokeWidth; final Gradient gradient; _ForegroundArcPainter({ required this.percentage, required this.strokeWidth, required this.gradient, }); @override void paint(Canvas canvas, Size size) { final rect = Rect.fromCircle( center: Offset(size.width / 2, size.height / 2), radius: (size.width - strokeWidth) / 2, ); final sweepAngle = 2 * 3.1415926 * (percentage / 100); final paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.round ..shader = gradient.createShader(rect); canvas.drawArc(rect, -0.5 * 3.1415926, sweepAngle, false, paint); } @override bool shouldRepaint(covariant _ForegroundArcPainter oldDelegate) { return oldDelegate.percentage != percentage || oldDelegate.strokeWidth != strokeWidth || oldDelegate.gradient != gradient; } } // 绘制指示点 class _IndicatorDotPainter extends CustomPainter { final double percentage; final double dotRadius; final Color color; _IndicatorDotPainter({ required this.percentage, required this.dotRadius, required this.color, }); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = (size.width / 2) - dotRadius; // 从顶部开始顺时针计算指示点角度 final angle = -0.5 * 3.1415926 + 2 * 3.1415926 * (percentage / 100); final dotCenter = Offset( center.dx + radius * cos(angle), center.dy + radius * sin(angle), ); final paint = Paint()..color = color; canvas.drawCircle(dotCenter, dotRadius, paint); } @override bool shouldRepaint(covariant _IndicatorDotPainter oldDelegate) { return oldDelegate.percentage != percentage || oldDelegate.dotRadius != dotRadius || oldDelegate.color != color; } } // 使用示例 void main() { runApp(const MaterialApp( home: Scaffold( body: Center( child: GradientArcIndicator(percentage: 75), ), ), )); }
关键说明
- 颜色匹配逻辑:通过
LinearGradient.transform方法,将百分比转换为渐变坐标,精准提取对应位置的颜色,保证指示点和文本颜色与弧形位置一致。 - 指示点定位:基于百分比计算对应的弧度,通过三角函数算出点在圆上的坐标,确保指示点落在弧形的对应百分比位置。
- 可定制性:支持调整弧形宽度、组件尺寸、渐变颜色组等参数,适配不同UI需求。
内容的提问来源于stack exchange,提问作者PNT
相关产品推荐
相关产品推荐

