This commit is contained in:
chenshuainan
2026-09-28 20:12:34 +08:00
parent 847986f861
commit 5f53bd8f94
60 changed files with 14624 additions and 398 deletions
@@ -0,0 +1,166 @@
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;
}
}