Flutter中为StrokeCap.butt/Square实现端点圆角的半环形图表方案
实现带端点圆角的半环形图表(保留StrokeCap.butt/Square基础样式)
针对你的需求,这里提供两种可行的Flutter实现方案,无需依赖SVG经验即可快速达成目标效果:
方案一:叠加路径实现端点圆角
最直观的方式是先绘制基础半环形(用StrokeCap.butt保证端点平直),再在两端叠加小圆形模拟圆角,完美匹配目标样式。
完整实现代码
import 'dart:math' as math; import 'package:flutter/material.dart'; class RoundedEndSemiCircle extends StatelessWidget { final double progress; // 0-1范围的进度值 final double strokeWidth; final Color color; final Color backgroundColor; final double size; const RoundedEndSemiCircle({ super.key, required this.progress, required this.strokeWidth, required this.color, required this.backgroundColor, required this.size, }); @override Widget build(BuildContext context) { return SizedBox( width: size, height: size, child: CustomPaint( painter: _RoundedEndSemiCirclePainter( progress: progress, strokeWidth: strokeWidth, color: color, backgroundColor: backgroundColor, ), ), ); } } class _RoundedEndSemiCirclePainter extends CustomPainter { final double progress; final double strokeWidth; final Color color; final Color backgroundColor; _RoundedEndSemiCirclePainter({ required this.progress, required this.strokeWidth, required this.color, required this.backgroundColor, }); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = (size.width - strokeWidth) / 2; final halfCircleAngle = math.pi; // 半环对应180度弧度 // 绘制背景半环 final backgroundPaint = Paint() ..color = backgroundColor ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.butt; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), math.pi, // 起始角度从左侧(180度)开始 halfCircleAngle, false, backgroundPaint, ); // 绘制进度半环(基础平直端点) final progressPaint = Paint() ..color = color ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.butt; final sweepAngle = halfCircleAngle * progress; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), math.pi, sweepAngle, false, progressPaint, ); // 计算并绘制两端圆角 final roundedRadius = strokeWidth / 2; // 圆角半径等于线宽的一半,视觉协调 // 起始端圆角(进度>0时才绘制) if (progress > 0) { final startOffset = Offset( center.dx + radius * math.cos(math.pi), center.dy + radius * math.sin(math.pi), ); canvas.drawCircle(startOffset, roundedRadius, progressPaint); } // 结束端圆角(进度>0时才绘制) if (progress > 0) { final endAngle = math.pi + sweepAngle; final endOffset = Offset( center.dx + radius * math.cos(endAngle), center.dy + radius * math.sin(endAngle), ); canvas.drawCircle(endOffset, roundedRadius, progressPaint); } } @override bool shouldRepaint(covariant _RoundedEndSemiCirclePainter oldDelegate) { return oldDelegate.progress != progress || oldDelegate.strokeWidth != strokeWidth || oldDelegate.color != color || oldDelegate.backgroundColor != backgroundColor; } }
使用示例
RoundedEndSemiCircle( progress: 0.7, strokeWidth: 20, color: Colors.blue, backgroundColor: Colors.grey.shade200, size: 200, )
方案二:自定义闭合路径构建带圆角的半环
如果不想叠加多个绘制元素,可以直接构建包含端点圆角的闭合路径,通过填充路径实现效果,性能更优。
核心代码片段(替换方案一中的进度绘制逻辑)
// 构建带圆角的进度路径 final path = Path(); final innerRadius = radius - strokeWidth; final roundedRadius = strokeWidth / 2; if (progress > 0) { final startAngle = math.pi; final endAngle = math.pi + sweepAngle; // 起始端圆角:从内侧起始点画圆弧到外侧起始点 final startInnerOffset = Offset( center.dx + innerRadius * math.cos(startAngle), center.dy + innerRadius * math.sin(startAngle), ); final startOuterOffset = Offset( center.dx + radius * math.cos(startAngle), center.dy + radius * math.sin(startAngle), ); path.moveTo(startInnerOffset.dx, startInnerOffset.dy); path.arcToPoint( startOuterOffset, radius: Radius.circular(roundedRadius), clockwise: false, ); // 绘制外侧进度圆弧 path.arcTo( Rect.fromCircle(center: center, radius: radius), startAngle, sweepAngle, false, ); // 结束端圆角:从外侧结束点画圆弧到内侧结束点 final endOuterOffset = Offset( center.dx + radius * math.cos(endAngle), center.dy + radius * math.sin(endAngle), ); final endInnerOffset = Offset( center.dx + innerRadius * math.cos(endAngle), center.dy + innerRadius * math.sin(endAngle), ); path.arcToPoint( endInnerOffset, radius: Radius.circular(roundedRadius), clockwise: false, ); // 绘制内侧圆弧闭合路径 path.arcTo( Rect.fromCircle(center: center, radius: innerRadius), endAngle, -sweepAngle, // 反向绘制内侧圆弧 false, ); } // 填充路径(注意这里把paint的style改成fill) canvas.drawPath(path, progressPaint..style = PaintingStyle.fill);
关键提示
- 圆角半径设置为
strokeWidth / 2时,视觉效果最贴合目标样式; - 记得判断
progress > 0再绘制圆角,避免进度为0时出现多余圆点; - 两种方案都保留了
StrokeCap.butt的基础端点特性,仅通过额外绘制实现圆角效果。
内容的提问来源于stack exchange,提问作者Youssef Hanna
相关产品推荐
相关产品推荐

