机器状态界面UI完成

This commit is contained in:
mmc
2026-03-04 14:56:17 +08:00
parent 7715d0b9fd
commit e69c9fee75

View File

@@ -47,12 +47,20 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
double _timeIndex = 0;
// 兼容低版本的 takeLast 方法
List<T> _takeLast<T>(List<T> list, int count) {
if (list.length <= count) {
return List.from(list);
// 核心修复:数据点X轴坐标强制映射为0-5(对应6个标签),保证一一对应
List<FlSpot> _getMappedSpots(List<FlSpot> data) {
final List<FlSpot> mapped = [];
// 取最后6个数据
final List<FlSpot> 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<FlSpot> list, {int max = 30}) {
@@ -121,7 +129,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
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<RunningStatusPage> {
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<RunningStatusPage> {
);
}
// ====================== 带动画的圆形仪表盘(尺寸缩小:原220→180,绘制尺寸280→240) ======================
// ====================== 带动画的圆形仪表盘 ======================
Widget _circularGauge({
required double value,
required double maxValue,
@@ -191,41 +194,54 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
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<LineChartBarData> lines,
required double yAxisMax,
double yAxisMin = 0,
List<String> bottomLabels = const ["t", "t+1", "t+2", "t+3", "t+4", "t+5"],
List<double> 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<double> yTicks = _calculateYTicks(yAxisMin, yAxisMax, yTickCount);
final List<double> 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<RunningStatusPage> {
],
),
),
// 修复溢出:用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<RunningStatusPage> {
);
}
// 刻度计算方法(确保0刻度被包含)
List<double> _calculateYTicks(double min, double max, int count) {
// 强制把最小值设为0(如果需要显示0刻度)
if (min > 0) {
min = 0;
}
final List<double> 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<RunningStatusPage> {
);
}
// 使用修复后的_getMappedSpots映射数据点
LineChartBarData _lineData(List<FlSpot> 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<RunningStatusPage> {
padding: const EdgeInsets.all(16),
child: Column(
children: [
// 电压仪表盘/折线图(使用新的chartCard,传入切换回调)
_chartCard(
title: "电压 (V)",
isGaugeMode: _voltageGaugeMode,
@@ -364,18 +409,18 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
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<RunningStatusPage> {
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<RunningStatusPage> {
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<RunningStatusPage> {
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<RunningStatusPage> {
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<RunningStatusPage> {
),
_gap(),
// 目标速度对比
Container(
decoration: BoxDecoration(
color: Colors.white,
@@ -479,7 +518,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
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<RunningStatusPage> {
),
_gap(),
// 电流对比
Container(
decoration: BoxDecoration(
color: Colors.white,
@@ -509,7 +547,8 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
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<RunningStatusPage> {
),
_gap(),
// 电机温度对比
Container(
decoration: BoxDecoration(
color: Colors.white,
@@ -539,7 +577,8 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
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<RunningStatusPage> {
),
_gap(),
// 航向角仪表盘(标题字体缩小+尺寸缩小)
Container(
decoration: BoxDecoration(
color: Colors.white,
@@ -596,7 +634,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
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<RunningStatusPage> {
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<RunningStatusPage> {
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<RunningStatusPage> {
}
}
// ====================== 带动画的圆形仪表盘 Widget(尺寸缩小:原220→180,绘制尺寸280→240) ======================
// ====================== 带动画的圆形仪表盘 Widget ======================
class AnimatedCircularGauge extends StatefulWidget {
final double value;
final double maxValue;
@@ -901,23 +935,19 @@ class _AnimatedCircularGaugeState extends State<AnimatedCircularGauge> 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<AnimatedCircularGauge> 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