Files
App.K12.Student.Miniapp_Flu…/lib/features/conquerPackage/report/widgets/semicircle_progress.dart
T
2026-09-28 20:12:34 +08:00

167 lines
5.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
/// ============================================================
/// 【SemicircleProgress】半圆正确率进度环
/// 1:1 对标 Vue components/lProgressCircle/lProgressCircle.vue
/// (report.vue 用法:width 308rpx / height 168rpx / strokeWidth 14 /
/// bgColor #D8DFEB / progressColor #1578FF,上半圆从左到右)
///
/// 尺寸说明(Vue canvas 逻辑像素 → Flutter dp):
/// 308rpx = 154.w,168rpx = 84.h,描边 14px = 7.w,
/// paddingTop 默认 8px = 4.h;
/// radius = min(宽/2 - 描边, 高 - 描边 - paddingTop)
/// ============================================================
class SemicircleProgress extends StatelessWidget {
const SemicircleProgress({
super.key,
required this.percent,
this.width = 154,
this.height = 84,
this.strokeWidth = 10,
this.paddingTop = 4,
this.trackColor = const Color(0xFFD8DFEB),
this.progressColor = const Color(0xFF1578FF),
});
/// 0 ~ 100
final num percent;
final double width;
final double height;
final double strokeWidth;
final double paddingTop;
final Color trackColor;
final Color progressColor;
@override
Widget build(BuildContext context) {
final w = width.w;
final h = height.h;
final sw = strokeWidth.w;
return SizedBox(
width: w,
height: h,
child: CustomPaint(
size: Size(w, h),
painter: _SemicirclePainter(
percent: percent.toDouble().clamp(0, 100),
strokeWidth: sw,
paddingTop: paddingTop.h,
trackColor: trackColor,
progressColor: progressColor,
),
// 对标 Vue slot:progress-content top 43rpx,居中展示数值
child: Padding(
padding: EdgeInsets.only(top: 31.5.h),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 大数字 + %(font-50 粗体标题色 + font-24 副色 %,底 10rpx)
Padding(
padding: EdgeInsets.only(bottom: 5.h),
child: RichText(
text: TextSpan(
children: [
TextSpan(
text: '${percent % 1 == 0 ? percent.toInt() : percent}',
style: TextStyle(
fontSize: 25.sp,
fontWeight: FontWeight.w600,
color: const Color(0xFF1D2B47),
),
),
TextSpan(
text: '%',
style: TextStyle(
fontSize: 12.sp,
color: const Color(0xFF495A7A),
),
),
],
),
),
),
Text(
'本次正确率',
style: TextStyle(
fontSize: 12.sp,
color: const Color(0xFF687FAD),
),
),
],
),
),
),
);
}
}
class _SemicirclePainter extends CustomPainter {
const _SemicirclePainter({
required this.percent,
required this.strokeWidth,
required this.paddingTop,
required this.trackColor,
required this.progressColor,
});
final double percent;
final double strokeWidth;
final double paddingTop;
final Color trackColor;
final Color progressColor;
@override
void paint(Canvas canvas, Size size) {
final halfStroke = strokeWidth / 2;
final maxRadiusByWidth = size.width / 2 - strokeWidth;
final maxRadiusByHeight = size.height - strokeWidth - paddingTop;
final radius = math.max(0.0, math.min(maxRadiusByWidth, maxRadiusByHeight));
final centerX = size.width / 2;
final centerY = size.height - halfStroke;
final trackPaint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round
..color = trackColor;
// 背景半圆环:π(左)→ 0(右)
canvas.drawArc(
Rect.fromCircle(center: Offset(centerX, centerY), radius: radius),
math.pi,
math.pi,
false,
trackPaint,
);
// 进度弧
if (percent > 0) {
final progressPaint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round
..color = progressColor;
final sweep = math.pi * (percent / 100);
canvas.drawArc(
Rect.fromCircle(center: Offset(centerX, centerY), radius: radius),
math.pi,
sweep,
false,
progressPaint,
);
}
}
@override
bool shouldRepaint(covariant _SemicirclePainter oldDelegate) {
return oldDelegate.percent != percent ||
oldDelegate.strokeWidth != strokeWidth ||
oldDelegate.trackColor != trackColor ||
oldDelegate.progressColor != progressColor;
}
}