From e69c9fee75230b22b6b0d0edffc6e4b2d056e89e Mon Sep 17 00:00:00 2001 From: mmc <1556375442@qq.com> Date: Wed, 4 Mar 2026 14:56:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=BA=E5=99=A8=E7=8A=B6=E6=80=81=E7=95=8C?= =?UTF-8?q?=E9=9D=A2UI=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/running_status_page.dart | 378 ++++++++++-------- 1 file changed, 215 insertions(+), 163 deletions(-) diff --git a/lib/features/home/presentation/pages/running_status_page.dart b/lib/features/home/presentation/pages/running_status_page.dart index f3a7caa0..f0183765 100644 --- a/lib/features/home/presentation/pages/running_status_page.dart +++ b/lib/features/home/presentation/pages/running_status_page.dart @@ -47,12 +47,20 @@ class _RunningStatusPageState extends State { double _timeIndex = 0; - // 兼容低版本的 takeLast 方法 - List _takeLast(List list, int count) { - if (list.length <= count) { - return List.from(list); + // 核心修复:数据点X轴坐标强制映射为0-5(对应6个标签),保证一一对应 + List _getMappedSpots(List data) { + final List mapped = []; + // 取最后6个数据 + final List limited = data.length > 6 ? data.sublist(data.length - 6) : List.from(data); + // 映射X轴坐标为0,1,2,3,4,5 + for (int i = 0; i < limited.length; i++) { + mapped.add(FlSpot(i.toDouble(), limited[i].y)); } - return list.sublist(list.length - count); + // 不足6个时,补空值(X轴继续递增,y=0) + while (mapped.length < 6) { + mapped.add(FlSpot(mapped.length.toDouble(), 0)); + } + return mapped; } void _limit(List list, {int max = 30}) { @@ -121,7 +129,7 @@ class _RunningStatusPageState extends State { return SizedBox(height: height); } - // ====================== 图表卡片(统一样式:标题字体缩小+标题与切换按钮同行) ====================== + // ====================== 图表卡片(统一样式) ====================== Widget _chartCard({required String title, required Widget child, required bool isGaugeMode, required Function() onGaugeTap, required Function() onChartTap}) { return Container( decoration: BoxDecoration( @@ -133,17 +141,12 @@ class _RunningStatusPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 标题与切换按钮同行布局 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( title, - style: const TextStyle( - fontSize: 16, // 标题字体缩小(原18) - fontWeight: FontWeight.bold, - color: Color(0xFF1677FF), - ), + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1677FF)), ), Row( children: [ @@ -181,7 +184,7 @@ class _RunningStatusPageState extends State { ); } - // ====================== 带动画的圆形仪表盘(尺寸缩小:原220→180,绘制尺寸280→240) ====================== + // ====================== 带动画的圆形仪表盘 ====================== Widget _circularGauge({ required double value, required double maxValue, @@ -191,41 +194,54 @@ class _RunningStatusPageState extends State { return AnimatedCircularGauge(value: value, maxValue: maxValue, unit: unit, majorTicks: majorTicks); } - // ====================== 罗盘式航向角仪表盘(尺寸同步缩小) ====================== + // ====================== 罗盘式航向角仪表盘 ====================== Widget _compassGauge({required double yaw}) { return SizedBox( - height: 200, // 原250→200 + height: 200, child: Stack( alignment: Alignment.center, children: [ CustomPaint( painter: CompassGaugePainter(yaw: yaw), - size: const Size(240, 240), // 原280→240 + size: const Size(240, 240), ), Text( "${yaw.toStringAsFixed(0)}°", - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black), // 字体同步缩小 + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black), ), ], ), ); } - // ====================== 折线图(修复溢出:限制宽度+裁剪+适配父容器) ====================== Widget _styledLineChart({ required String title, required List lines, required double yAxisMax, double yAxisMin = 0, List bottomLabels = const ["t", "t+1", "t+2", "t+3", "t+4", "t+5"], - List leftTicks = const [0, 1000, 2000, 3000], + int yTickCount = 5, + bool forceShowZeroTick = true, + // 兼容版:控制刻度文字的内边距(替代axisOffset) + double leftTitlePadding = 8.0, + double bottomTitlePadding = 8.0, }) { - // 获取实际显示的点位数量 - final int showSpotCount = lines.isNotEmpty ? lines.first.spots.length : 0; - // 计算底部标签的间隔,避免标签过多溢出 - final double bottomInterval = showSpotCount > 6 ? showSpotCount / 6 : 1; + // 确保0刻度被包含在刻度列表中 + final List yTicks = _calculateYTicks(yAxisMin, yAxisMax, yTickCount); + final List finalYTicks = forceShowZeroTick + ? (yTicks.contains(0) ? yTicks : [...yTicks, 0] + ..sort()) + : yTicks; + + // 计算合法的刻度间隔(必须>0) + final double yInterval = finalYTicks.length > 1 ? (finalYTicks.last - finalYTicks.first) / (finalYTicks.length - 1) : 1.0; + + const double xMin = 0; + const double xMax = 5; return Column( + crossAxisAlignment: CrossAxisAlignment.start, // 整体左对齐,保证标题和刻度对齐 + mainAxisSize: MainAxisSize.min, // 去除列的额外空白 children: [ if (lines.length > 1) Padding( @@ -239,69 +255,81 @@ class _RunningStatusPageState extends State { ], ), ), - // 修复溢出:用ConstrainedBox限制宽度+ClipRect裁剪+Expanded适配 Expanded( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: double.infinity), - child: ClipRect( - child: SizedBox( - height: 180, // 原200→180,缩小折线图高度 - child: LineChart( - LineChartData( - minY: yAxisMin, - maxY: yAxisMax, - minX: 0, - maxX: bottomLabels.length - 1.toDouble(), // 固定X轴范围,防止溢出 - gridData: FlGridData( - show: true, - horizontalInterval: (yAxisMax - yAxisMin) / leftTicks.length, - getDrawingHorizontalLine: (value) => const FlLine(color: Color(0xFFE5E5E5), strokeWidth: 1), - drawVerticalLine: false, - ), - titlesData: FlTitlesData( - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - interval: (yAxisMax - yAxisMin) / leftTicks.length, - getTitlesWidget: (value, meta) { - if (leftTicks.contains(value)) { - return Text(value.toStringAsFixed(0), style: const TextStyle(fontSize: 12, color: Colors.grey)); - } - return const SizedBox.shrink(); - }, - reservedSize: 40, - ), - ), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - interval: bottomInterval, - reservedSize: 30, - getTitlesWidget: (value, meta) { - int idx = value.toInt(); - if (idx >= 0 && idx < bottomLabels.length) { - return Container( - width: 40, - alignment: Alignment.center, - child: Text( - bottomLabels[idx], - style: const TextStyle(fontSize: 12, color: Colors.grey), - overflow: TextOverflow.ellipsis, - ), - ); - } - return const SizedBox.shrink(); - }, - ), - ), - rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), - topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), - ), - borderData: FlBorderData(show: false), - lineBarsData: lines, - extraLinesData: ExtraLinesData(horizontalLines: []), - ), + child: Padding( + // 右侧预留空间显示最后一个刻度 + padding: const EdgeInsets.only(right: 35, top: 5, bottom: 0, left: 0), + child: LineChart( + LineChartData( + minY: yAxisMin, + maxY: yAxisMax, + minX: xMin, + maxX: xMax, + borderData: FlBorderData(show: false), + gridData: FlGridData( + show: true, + horizontalInterval: yInterval, + getDrawingHorizontalLine: (value) { + // 0刻度网格线加粗突出 + if (value == 0) { + return const FlLine(color: Color(0xFFCCCCCC), strokeWidth: 1.5); + } + return const FlLine(color: Color(0xFFE5E5E5), strokeWidth: 1); + }, + drawVerticalLine: false, ), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + // 预留刻度文字宽度(关键:避免文字被截断) + reservedSize: 50 + leftTitlePadding, + // 合法的间隔值(>0) + interval: yInterval, + getTitlesWidget: (value, meta) { + // 只显示目标刻度(包含0) + if (finalYTicks.any((tick) => (value - tick).abs() < 0.01)) { + // 用Padding控制刻度与图表的间距(兼容所有版本) + return Padding( + padding: EdgeInsets.only(right: leftTitlePadding), + child: Text( + value.toStringAsFixed(0), + style: const TextStyle(fontSize: 12, color: Colors.grey), + textAlign: TextAlign.right, + ), + ); + } + return const SizedBox.shrink(); + }, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + interval: 1, // 横轴固定间隔1(合法值) + // 预留刻度文字高度 + reservedSize: 35 + bottomTitlePadding, + getTitlesWidget: (value, meta) { + int idx = value.toInt(); + if (idx >= 0 && idx < bottomLabels.length) { + return Padding( + // 用Padding控制垂直间距,Transform避免最后一个标签截断 + padding: EdgeInsets.only(top: bottomTitlePadding), + child: Transform.translate( + offset: const Offset(-5, 0), + child: Text(bottomLabels[idx], style: const TextStyle(fontSize: 12, color: Colors.grey)), + ), + ); + } + return const SizedBox.shrink(); + }, + ), + ), + rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), + topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), + ), + lineBarsData: lines, + extraLinesData: ExtraLinesData(horizontalLines: []), ), ), ), @@ -310,6 +338,23 @@ class _RunningStatusPageState extends State { ); } + // 刻度计算方法(确保0刻度被包含) + List _calculateYTicks(double min, double max, int count) { + // 强制把最小值设为0(如果需要显示0刻度) + if (min > 0) { + min = 0; + } + + final List ticks = []; + final double step = (max - min) / (count - 1); + + for (int i = 0; i < count; i++) { + ticks.add(min + step * i); + } + + return ticks; + } + Widget _legendItem({required Color color, required String label}) { return Row( children: [ @@ -328,9 +373,10 @@ class _RunningStatusPageState extends State { ); } + // 使用修复后的_getMappedSpots映射数据点 LineChartBarData _lineData(List data, Color color) { return LineChartBarData( - spots: _takeLast(data, 6), + spots: _getMappedSpots(data), isCurved: false, color: color, barWidth: 2, @@ -355,7 +401,6 @@ class _RunningStatusPageState extends State { padding: const EdgeInsets.all(16), child: Column( children: [ - // 电压仪表盘/折线图(使用新的chartCard,传入切换回调) _chartCard( title: "电压 (V)", isGaugeMode: _voltageGaugeMode, @@ -364,18 +409,18 @@ class _RunningStatusPageState extends State { child: _voltageGaugeMode ? _circularGauge(value: status.voltage, maxValue: 250, unit: "V", majorTicks: const [0, 50, 100, 150, 200, 250]) : SizedBox( - height: 180, // 原200→180 + height: 180, child: _styledLineChart( title: "电压", lines: [_lineData(_voltageHistory, const Color(0xFF4CAF50))], yAxisMax: 250, - leftTicks: const [0, 50, 100, 150, 200, 250], + yAxisMin: 0, + yTickCount: 6, ), ), ), _gap(), - // 芯片温度仪表盘/折线图 _chartCard( title: "芯片温度 (°C)", isGaugeMode: _chipTempGaugeMode, @@ -389,13 +434,13 @@ class _RunningStatusPageState extends State { title: "芯片温度", lines: [_lineData(_chipTempHistory, const Color(0xFF4CAF50))], yAxisMax: 100, - leftTicks: const [0, 20, 40, 60, 80, 100], + yAxisMin: 0, + yTickCount: 6, ), ), ), _gap(), - // 割刀速度仪表盘/折线图 _chartCard( title: "割刀速度 (rpm)", isGaugeMode: _knifeSpeedGaugeMode, @@ -415,13 +460,12 @@ class _RunningStatusPageState extends State { lines: [_lineData(_knifeHistory, const Color(0xFF4CAF50))], yAxisMax: 3000, yAxisMin: -3000, - leftTicks: const [-3000, -2000, -1000, 0, 1000, 2000, 3000], + yTickCount: 7, ), ), ), _gap(), - // 测量速度对比(无切换按钮,使用原布局+标题字体缩小) Container( decoration: BoxDecoration( color: Colors.white, @@ -434,11 +478,7 @@ class _RunningStatusPageState extends State { children: [ Text( "左右轮测量速度对比 (rpm)", - style: const TextStyle( - fontSize: 16, // 标题字体缩小 - fontWeight: FontWeight.bold, - color: Color(0xFF1677FF), - ), + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1677FF)), ), const SizedBox(height: 16), SizedBox( @@ -448,7 +488,7 @@ class _RunningStatusPageState extends State { lines: [_lineData(_leftMeasureHistory, const Color(0xFF3F51B5)), _lineData(_rightMeasureHistory, const Color(0xFF8BC34A))], yAxisMax: 3000, yAxisMin: -3000, - leftTicks: const [-3000, -2000, -1000, 0, 1000, 2000, 3000], + yTickCount: 7, ), ), ], @@ -456,7 +496,6 @@ class _RunningStatusPageState extends State { ), _gap(), - // 目标速度对比 Container( decoration: BoxDecoration( color: Colors.white, @@ -479,7 +518,7 @@ class _RunningStatusPageState extends State { lines: [_lineData(_leftTargetHistory, const Color(0xFF3F51B5)), _lineData(_rightTargetHistory, const Color(0xFF8BC34A))], yAxisMax: 3000, yAxisMin: -3000, - leftTicks: const [-3000, -2000, -1000, 0, 1000, 2000, 3000], + yTickCount: 7, ), ), ], @@ -487,7 +526,6 @@ class _RunningStatusPageState extends State { ), _gap(), - // 电流对比 Container( decoration: BoxDecoration( color: Colors.white, @@ -509,7 +547,8 @@ class _RunningStatusPageState extends State { title: "电流对比", lines: [_lineData(_leftCurrentHistory, const Color(0xFF3F51B5)), _lineData(_rightCurrentHistory, const Color(0xFF8BC34A))], yAxisMax: 100, - leftTicks: const [0, 20, 40, 60, 80, 100], + yAxisMin: 0, + yTickCount: 6, ), ), ], @@ -517,7 +556,6 @@ class _RunningStatusPageState extends State { ), _gap(), - // 电机温度对比 Container( decoration: BoxDecoration( color: Colors.white, @@ -539,7 +577,8 @@ class _RunningStatusPageState extends State { title: "电机温度对比", lines: [_lineData(_leftTempHistory, const Color(0xFF3F51B5)), _lineData(_rightTempHistory, const Color(0xFF8BC34A))], yAxisMax: 100, - leftTicks: const [0, 20, 40, 60, 80, 100], + yAxisMin: 0, + yTickCount: 6, ), ), ], @@ -547,7 +586,6 @@ class _RunningStatusPageState extends State { ), _gap(), - // 航向角仪表盘(标题字体缩小+尺寸缩小) Container( decoration: BoxDecoration( color: Colors.white, @@ -596,7 +634,7 @@ class _RunningStatusPageState extends State { children: [ const Padding( padding: EdgeInsets.symmetric(vertical: 12), - child: Text("电机参数", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), // 标题字体缩小 + child: Text("电机参数", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), ), Table( border: TableBorder.all(color: const Color(0xFFE5E5E5)), @@ -647,7 +685,7 @@ class _RunningStatusPageState extends State { children: [ const Padding( padding: EdgeInsets.symmetric(vertical: 12), - child: Text("其他参数", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), // 标题字体缩小 + child: Text("其他参数", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), ), Table( border: TableBorder.all(color: const Color(0xFFE5E5E5)), @@ -833,11 +871,7 @@ class _RunningStatusPageState extends State { if (state is DeviceStatusUpdated) { _appendChartData(state); } - return Container( - margin: const EdgeInsets.all(16), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)), - child: _isCardView ? _buildCardContentView(state) : _buildChartContentView(state), - ); + return Container(margin: const EdgeInsets.all(8), child: _isCardView ? _buildCardContentView(state) : _buildChartContentView(state)); }, ), ), @@ -847,7 +881,7 @@ class _RunningStatusPageState extends State { } } -// ====================== 带动画的圆形仪表盘 Widget(尺寸缩小:原220→180,绘制尺寸280→240) ====================== +// ====================== 带动画的圆形仪表盘 Widget ====================== class AnimatedCircularGauge extends StatefulWidget { final double value; final double maxValue; @@ -901,23 +935,19 @@ class _AnimatedCircularGaugeState extends State with Sing @override Widget build(BuildContext context) { return SizedBox( - height: 180, // 原220→180 + height: 180, child: Stack( alignment: Alignment.center, children: [ CustomPaint( painter: CircularGaugePainter(value: _valueAnimation.value, maxValue: widget.maxValue, majorTicks: widget.majorTicks), - size: const Size(240, 240), // 原280→240 + size: const Size(240, 240), ), Positioned( bottom: 20, child: Text( "${_valueAnimation.value.toStringAsFixed(2)} ${widget.unit}", - style: const TextStyle( - fontSize: 18, // 原20→18 - fontWeight: FontWeight.w500, - color: Colors.grey, - ), + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500, color: Colors.grey), ), ), ], @@ -926,7 +956,7 @@ class _AnimatedCircularGaugeState extends State with Sing } } -// ====================== 圆形仪表盘绘制器(尺寸同步缩小) ====================== +// ====================== 圆形仪表盘绘制器 ====================== class CircularGaugePainter extends CustomPainter { final double value; final double maxValue; @@ -939,7 +969,6 @@ class CircularGaugePainter extends CustomPainter { final center = Offset(size.width / 2, size.height / 2); final radius = size.width / 2 - 20; - // 背景圆弧 final bgArcPaint = Paint() ..color = const Color(0xFFE3F2FD) ..style = PaintingStyle.stroke @@ -948,7 +977,6 @@ class CircularGaugePainter extends CustomPainter { final bgRect = Rect.fromCircle(center: center, radius: radius); canvas.drawArc(bgRect, 135 * math.pi / 180, 270 * math.pi / 180, false, bgArcPaint); - // 进度圆弧 final progressRatio = math.min(value / maxValue, 1.0); final progressPaint = Paint() ..color = const Color(0xFFBBDEFB) @@ -957,7 +985,6 @@ class CircularGaugePainter extends CustomPainter { ..strokeCap = StrokeCap.round; canvas.drawArc(bgRect, 135 * math.pi / 180, 270 * math.pi / 180 * progressRatio, false, progressPaint); - // 刻度 final tickPaint = Paint()..color = Colors.grey.shade400; final majorTickPaint = Paint() ..color = Colors.grey.shade600 @@ -981,11 +1008,7 @@ class CircularGaugePainter extends CustomPainter { final textPainter = TextPainter( text: TextSpan( text: textValue, - style: const TextStyle( - fontSize: 12, // 原14→12,刻度数值缩小 - color: Colors.grey, - fontWeight: FontWeight.w500, - ), + style: const TextStyle(fontSize: 12, color: Colors.grey, fontWeight: FontWeight.w500), ), textDirection: TextDirection.ltr, )..layout(); @@ -996,30 +1019,22 @@ class CircularGaugePainter extends CustomPainter { } } - // 指针角度 final needleAngle = 135 * math.pi / 180 + 270 * math.pi / 180 * progressRatio; - - // 三角形指针 final needlePaint = Paint() ..color = Colors.grey.shade600 ..style = PaintingStyle.fill; final needlePath = Path() ..moveTo(center.dx + (radius - 10) * math.cos(needleAngle), center.dy + (radius - 10) * math.sin(needleAngle)) - ..lineTo( - center.dx + 6 * math.cos(needleAngle - math.pi / 2), // 原8→6,指针底边宽度缩小 - center.dy + 6 * math.sin(needleAngle - math.pi / 2), - ) + ..lineTo(center.dx + 6 * math.cos(needleAngle - math.pi / 2), center.dy + 6 * math.sin(needleAngle - math.pi / 2)) ..lineTo(center.dx + 6 * math.cos(needleAngle + math.pi / 2), center.dy + 6 * math.sin(needleAngle + math.pi / 2)) ..close(); canvas.drawPath(needlePath, needlePaint); - - // 中心圆点 final centerDotPaint = Paint() ..color = Colors.white ..style = PaintingStyle.fill; - canvas.drawCircle(center, 3, centerDotPaint); // 原4→3,圆点缩小 + canvas.drawCircle(center, 3, centerDotPaint); } @override @@ -1028,7 +1043,8 @@ class CircularGaugePainter extends CustomPainter { } } -// ====================== 罗盘式航向角仪表盘绘制器(尺寸缩小) ====================== +// ====================== 罗盘式航向角仪表盘绘制器 ====================== + class CompassGaugePainter extends CustomPainter { final double yaw; @@ -1039,20 +1055,23 @@ class CompassGaugePainter extends CustomPainter { final center = Offset(size.width / 2, size.height / 2); final radius = size.width / 2 - 20; - // 外圈 + // 绘制外圆 final outerPaint = Paint() ..color = const Color(0xFF1677FF) ..style = PaintingStyle.stroke - ..strokeWidth = 10; // 原12→10,外圈宽度缩小 + ..strokeWidth = 10; canvas.drawCircle(center, radius, outerPaint); - // 刻度 + // 绘制刻度 final tickPaint = Paint() ..color = Colors.grey ..strokeWidth = 2; for (int i = 0; i < 36; i++) { - final angle = (i * 10 - 90) * math.pi / 180; + // 修复1:刻度角度映射 - 让0°=北(N),180°=南(S),90°=东(E),270°=西(W) + final degree = i * 10; + final angle = (degree - 90) * math.pi / 180; // 0°(北)对应-90弧度,180°(南)对应90弧度 + final x1 = center.dx + (radius - 10) * math.cos(angle); final y1 = center.dy + (radius - 10) * math.sin(angle); final x2 = center.dx + radius * math.cos(angle); @@ -1060,24 +1079,28 @@ class CompassGaugePainter extends CustomPainter { canvas.drawLine(Offset(x1, y1), Offset(x2, y2), tickPaint); if (i % 9 == 0) { + // 绘制主刻度 final x3 = center.dx + (radius - 20) * math.cos(angle); final y3 = center.dy + (radius - 20) * math.sin(angle); canvas.drawLine(Offset(x1, y1), Offset(x3, y3), tickPaint..strokeWidth = 4); - final direction = i == 0 - ? "N" - : i == 9 - ? "E" - : i == 18 - ? "S" - : i == 27 - ? "W" - : ""; + // 绘制方向文字(N/E/S/W)和角度数值 + String direction = ""; + if (degree == 0) + direction = "N"; + else if (degree == 90) + direction = "E"; + else if (degree == 180) + direction = "S"; + else if (degree == 270) + direction = "W"; + + // 绘制方向文字 if (direction.isNotEmpty) { final textPainter = TextPainter( text: TextSpan( text: direction, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black), // 原18→16 + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black), ), textDirection: TextDirection.ltr, )..layout(); @@ -1086,11 +1109,11 @@ class CompassGaugePainter extends CustomPainter { textPainter.paint(canvas, Offset(textX, textY)); } - final angleValue = (i * 10).toString(); + // 绘制角度数值(如90、180、270) final textPainter = TextPainter( text: TextSpan( - text: angleValue, - style: const TextStyle(fontSize: 12, color: Colors.black54), // 原14→12 + text: degree.toString(), + style: const TextStyle(fontSize: 12, color: Colors.black54), ), textDirection: TextDirection.ltr, )..layout(); @@ -1100,19 +1123,48 @@ class CompassGaugePainter extends CustomPainter { } } - // 指针 - final needleAngle = (yaw - 90) * math.pi / 180; + // 绘制中心角度文字(如180°) + final centerTextPainter = TextPainter( + text: TextSpan( + text: "${yaw.toStringAsFixed(0)}°", + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black), + ), + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + )..layout(); + final centerTextX = center.dx - centerTextPainter.width / 2; + final centerTextY = center.dy - centerTextPainter.height / 2; + centerTextPainter.paint(canvas, Offset(centerTextX, centerTextY)); + + // ========== 核心修复:指针角度计算 ========== + // 修复2:指针角度映射 - 让yaw=180°时指向正南(S) + final needleDegree = yaw; + final needleAngle = (needleDegree - 90) * math.pi / 180; // 与刻度角度映射一致 + + // 指针绘制样式 final needlePaint = Paint() ..color = Colors.red ..style = PaintingStyle.fill; + // 指针路径:粗端在中心,细端指向外部 final needlePath = Path() - ..moveTo(center.dx, center.dy) - ..lineTo(center.dx + radius * 0.7 * math.cos(needleAngle - 0.03), center.dy + radius * 0.7 * math.sin(needleAngle - 0.03)) - ..lineTo(center.dx + radius * 0.7 * math.cos(needleAngle + 0.03), center.dy + radius * 0.7 * math.sin(needleAngle + 0.03)) + ..moveTo(center.dx - 8, center.dy) // 中心左侧8px + ..lineTo(center.dx + 8, center.dy) // 中心右侧8px + ..lineTo(center.dx + radius * 0.8 * math.cos(needleAngle), center.dy + radius * 0.8 * math.sin(needleAngle)) ..close(); canvas.drawPath(needlePath, needlePaint); + + // 中心圆点美化 + final centerDotPaint = Paint() + ..color = Colors.white + ..style = PaintingStyle.fill; + canvas.drawCircle(center, 4, centerDotPaint); + final centerBorderPaint = Paint() + ..color = Colors.red + ..style = PaintingStyle.stroke + ..strokeWidth = 2; + canvas.drawCircle(center, 4, centerBorderPaint); } @override