机器状态界面UI完成
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user