大小样式调整

This commit is contained in:
mmc
2026-03-04 14:00:40 +08:00
parent cd6b9a59ff
commit 7715d0b9fd

View File

@@ -8,6 +8,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncfusion_flutter_gauges/gauges.dart';
import 'package:flutter/animation.dart';
// 占位导入(请替换为你项目的实际路径)
import '../../../../core/app/app_user_cubit.dart';
import '../../../../core/di/injection.dart';
import '../../../../core/network/net_message_dispatcher.dart';
@@ -120,8 +121,8 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
return SizedBox(height: height);
}
// ====================== 图表卡片(统一样式) ======================
Widget _chartCard({required String title, required Widget child}) {
// ====================== 图表卡片(统一样式:标题字体缩小+标题与切换按钮同行) ======================
Widget _chartCard({required String title, required Widget child, required bool isGaugeMode, required Function() onGaugeTap, required Function() onChartTap}) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
@@ -132,9 +133,46 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF1677FF)),
// 标题与切换按钮同行布局
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: const TextStyle(
fontSize: 16, // 标题字体缩小(原18)
fontWeight: FontWeight.bold,
color: Color(0xFF1677FF),
),
),
Row(
children: [
GestureDetector(
onTap: onGaugeTap,
child: Text(
"仪表盘",
style: TextStyle(
color: isGaugeMode ? const Color(0xFF1677FF) : Colors.grey,
fontSize: 14,
fontWeight: isGaugeMode ? FontWeight.bold : FontWeight.normal,
),
),
),
const SizedBox(width: 16),
GestureDetector(
onTap: onChartTap,
child: Text(
"折线图",
style: TextStyle(
color: !isGaugeMode ? const Color(0xFF1677FF) : Colors.grey,
fontSize: 14,
fontWeight: !isGaugeMode ? FontWeight.bold : FontWeight.normal,
),
),
),
],
),
],
),
const SizedBox(height: 16),
child,
@@ -143,38 +181,37 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
);
}
// ====================== 带动画的圆形仪表盘(核心修改) ======================
// ====================== 带动画的圆形仪表盘(尺寸缩小:原220→180,绘制尺寸280→240) ======================
Widget _circularGauge({
required double value,
required double maxValue,
required String unit,
required String label,
List<double> majorTicks = const [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
}) {
return AnimatedCircularGauge(value: value, maxValue: maxValue, unit: unit, majorTicks: majorTicks);
}
// ====================== 罗盘式航向角仪表盘 ======================
// ====================== 罗盘式航向角仪表盘(尺寸同步缩小) ======================
Widget _compassGauge({required double yaw}) {
return SizedBox(
height: 250,
height: 200, // 原250→200
child: Stack(
alignment: Alignment.center,
children: [
CustomPaint(
painter: CompassGaugePainter(yaw: yaw),
size: const Size(280, 280),
size: const Size(240, 240), // 原280→240
),
Text(
"${yaw.toStringAsFixed(0)}°",
style: const TextStyle(fontSize: 24, 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,
@@ -202,67 +239,69 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
],
),
),
// 用Expanded适配宽度,避免溢出
// 修复溢出:用ConstrainedBox限制宽度+ClipRect裁剪+Expanded适配
Expanded(
child: SizedBox(
height: 200,
child: LineChart(
LineChartData(
minY: yAxisMin,
maxY: yAxisMax,
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,
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,
),
),
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();
},
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: []),
),
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
),
borderData: FlBorderData(show: false),
lineBarsData: lines,
// 设置图表边距,避免内容贴边
minX: 0,
maxX: bottomLabels.length - 1.toDouble(),
extraLinesData: ExtraLinesData(horizontalLines: []),
),
),
),
@@ -316,240 +355,217 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
padding: const EdgeInsets.all(16),
child: Column(
children: [
// 电压仪表盘/折线图
// 电压仪表盘/折线图(使用新的chartCard,传入切换回调)
_chartCard(
title: "电压 (V)",
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () => setState(() => _voltageGaugeMode = true),
child: Text(
"仪表盘",
style: TextStyle(
color: _voltageGaugeMode ? const Color(0xFF1677FF) : Colors.grey,
fontSize: 14,
fontWeight: _voltageGaugeMode ? FontWeight.bold : FontWeight.normal,
),
),
isGaugeMode: _voltageGaugeMode,
onGaugeTap: () => setState(() => _voltageGaugeMode = true),
onChartTap: () => setState(() => _voltageGaugeMode = false),
child: _voltageGaugeMode
? _circularGauge(value: status.voltage, maxValue: 250, unit: "V", majorTicks: const [0, 50, 100, 150, 200, 250])
: SizedBox(
height: 180, // 原200→180
child: _styledLineChart(
title: "电压",
lines: [_lineData(_voltageHistory, const Color(0xFF4CAF50))],
yAxisMax: 250,
leftTicks: const [0, 50, 100, 150, 200, 250],
),
const SizedBox(width: 16),
GestureDetector(
onTap: () => setState(() => _voltageGaugeMode = false),
child: Text(
"折线图",
style: TextStyle(
color: !_voltageGaugeMode ? const Color(0xFF1677FF) : Colors.grey,
fontSize: 14,
fontWeight: !_voltageGaugeMode ? FontWeight.bold : FontWeight.normal,
),
),
),
],
),
const SizedBox(height: 16),
_voltageGaugeMode
? _circularGauge(value: status.voltage, maxValue: 250, unit: "V", label: "电压", majorTicks: const [0, 50, 100, 150, 200, 250])
: SizedBox(
height: 200,
child: _styledLineChart(
title: "电压",
lines: [_lineData(_voltageHistory, const Color(0xFF4CAF50))],
yAxisMax: 250,
leftTicks: const [0, 50, 100, 150, 200, 250],
),
),
],
),
),
),
_gap(),
// 芯片温度仪表盘/折线图
_chartCard(
title: "芯片温度 (°C)",
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () => setState(() => _chipTempGaugeMode = true),
child: Text(
"仪表盘",
style: TextStyle(
color: _chipTempGaugeMode ? const Color(0xFF1677FF) : Colors.grey,
fontSize: 14,
fontWeight: _chipTempGaugeMode ? FontWeight.bold : FontWeight.normal,
),
),
isGaugeMode: _chipTempGaugeMode,
onGaugeTap: () => setState(() => _chipTempGaugeMode = true),
onChartTap: () => setState(() => _chipTempGaugeMode = false),
child: _chipTempGaugeMode
? _circularGauge(value: status.chipTemp, maxValue: 100, unit: "°C", majorTicks: const [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
: SizedBox(
height: 180,
child: _styledLineChart(
title: "芯片温度",
lines: [_lineData(_chipTempHistory, const Color(0xFF4CAF50))],
yAxisMax: 100,
leftTicks: const [0, 20, 40, 60, 80, 100],
),
const SizedBox(width: 16),
GestureDetector(
onTap: () => setState(() => _chipTempGaugeMode = false),
child: Text(
"折线图",
style: TextStyle(
color: !_chipTempGaugeMode ? const Color(0xFF1677FF) : Colors.grey,
fontSize: 14,
fontWeight: !_chipTempGaugeMode ? FontWeight.bold : FontWeight.normal,
),
),
),
],
),
const SizedBox(height: 16),
_chipTempGaugeMode
? _circularGauge(
value: status.chipTemp,
maxValue: 100,
unit: "°C",
label: "芯片温度",
majorTicks: const [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
)
: SizedBox(
height: 200,
child: _styledLineChart(
title: "芯片温度",
lines: [_lineData(_chipTempHistory, const Color(0xFF4CAF50))],
yAxisMax: 100,
leftTicks: const [0, 20, 40, 60, 80, 100],
),
),
],
),
),
),
_gap(),
// 割刀速度仪表盘/折线图
_chartCard(
title: "割刀速度 (rpm)",
isGaugeMode: _knifeSpeedGaugeMode,
onGaugeTap: () => setState(() => _knifeSpeedGaugeMode = true),
onChartTap: () => setState(() => _knifeSpeedGaugeMode = false),
child: _knifeSpeedGaugeMode
? _circularGauge(
value: double.tryParse(status.knifeCuttingSpeed) ?? 0,
maxValue: 3000,
unit: "rpm",
majorTicks: const [0, 500, 1000, 1500, 2000, 2500, 3000],
)
: SizedBox(
height: 180,
child: _styledLineChart(
title: "割刀速度",
lines: [_lineData(_knifeHistory, const Color(0xFF4CAF50))],
yAxisMax: 3000,
yAxisMin: -3000,
leftTicks: const [-3000, -2000, -1000, 0, 1000, 2000, 3000],
),
),
),
_gap(),
// 测量速度对比(无切换按钮,使用原布局+标题字体缩小)
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2))],
),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () => setState(() => _knifeSpeedGaugeMode = true),
child: Text(
"仪表盘",
style: TextStyle(
color: _knifeSpeedGaugeMode ? const Color(0xFF1677FF) : Colors.grey,
fontSize: 14,
fontWeight: _knifeSpeedGaugeMode ? FontWeight.bold : FontWeight.normal,
),
),
),
const SizedBox(width: 16),
GestureDetector(
onTap: () => setState(() => _knifeSpeedGaugeMode = false),
child: Text(
"折线图",
style: TextStyle(
color: !_knifeSpeedGaugeMode ? const Color(0xFF1677FF) : Colors.grey,
fontSize: 14,
fontWeight: !_knifeSpeedGaugeMode ? FontWeight.bold : FontWeight.normal,
),
),
),
],
Text(
"左右轮测量速度对比 (rpm)",
style: const TextStyle(
fontSize: 16, // 标题字体缩小
fontWeight: FontWeight.bold,
color: Color(0xFF1677FF),
),
),
const SizedBox(height: 16),
_knifeSpeedGaugeMode
? _circularGauge(
value: double.tryParse(status.knifeCuttingSpeed) ?? 0,
maxValue: 3000,
unit: "rpm",
label: "割刀速度",
majorTicks: const [0, 500, 1000, 1500, 2000, 2500, 3000],
)
: SizedBox(
height: 200,
child: _styledLineChart(
title: "割刀速度",
lines: [_lineData(_knifeHistory, const Color(0xFF4CAF50))],
yAxisMax: 3000,
yAxisMin: -3000,
leftTicks: const [-3000, -2000, -1000, 0, 1000, 2000, 3000],
),
),
SizedBox(
height: 180,
child: _styledLineChart(
title: "左右轮测量速度对比",
lines: [_lineData(_leftMeasureHistory, const Color(0xFF3F51B5)), _lineData(_rightMeasureHistory, const Color(0xFF8BC34A))],
yAxisMax: 3000,
yAxisMin: -3000,
leftTicks: const [-3000, -2000, -1000, 0, 1000, 2000, 3000],
),
),
],
),
),
_gap(),
// 测量速度对比(左轮 + 右轮在一张图)
_chartCard(
title: "左右轮测量速度对比 (rpm)",
child: SizedBox(
height: 200,
child: _styledLineChart(
title: "左右轮测量速度对比",
lines: [
_lineData(_leftMeasureHistory, const Color(0xFF3F51B5)), // 左轮
_lineData(_rightMeasureHistory, const Color(0xFF8BC34A)), // 右轮
],
yAxisMax: 3000,
yAxisMin: -3000,
leftTicks: const [-3000, -2000, -1000, 0, 1000, 2000, 3000],
),
// 目标速度对比
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2))],
),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"左右轮目标速度对比 (rpm)",
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1677FF)),
),
const SizedBox(height: 16),
SizedBox(
height: 180,
child: _styledLineChart(
title: "左右轮目标速度对比",
lines: [_lineData(_leftTargetHistory, const Color(0xFF3F51B5)), _lineData(_rightTargetHistory, const Color(0xFF8BC34A))],
yAxisMax: 3000,
yAxisMin: -3000,
leftTicks: const [-3000, -2000, -1000, 0, 1000, 2000, 3000],
),
),
],
),
),
_gap(),
// 目标速度对比(左轮 + 右轮在一张图)
_chartCard(
title: "左右轮目标速度对比 (rpm)",
child: SizedBox(
height: 200,
child: _styledLineChart(
title: "左右轮目标速度对比",
lines: [
_lineData(_leftTargetHistory, const Color(0xFF3F51B5)), // 左轮
_lineData(_rightTargetHistory, const Color(0xFF8BC34A)), // 右轮
],
yAxisMax: 3000,
yAxisMin: -3000,
leftTicks: const [-3000, -2000, -1000, 0, 1000, 2000, 3000],
),
),
),
_gap(),
// 电流对比
_chartCard(
title: "电流对比 (A)",
child: SizedBox(
height: 200,
child: _styledLineChart(
title: "电流对比",
lines: [_lineData(_leftCurrentHistory, const Color(0xFF3F51B5)), _lineData(_rightCurrentHistory, const Color(0xFF8BC34A))],
yAxisMax: 100,
leftTicks: const [0, 20, 40, 60, 80, 100],
),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2))],
),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"电流对比 (A)",
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1677FF)),
),
const SizedBox(height: 16),
SizedBox(
height: 180,
child: _styledLineChart(
title: "电流对比",
lines: [_lineData(_leftCurrentHistory, const Color(0xFF3F51B5)), _lineData(_rightCurrentHistory, const Color(0xFF8BC34A))],
yAxisMax: 100,
leftTicks: const [0, 20, 40, 60, 80, 100],
),
),
],
),
),
_gap(),
// 电机温度对比
_chartCard(
title: "电机温度对比 (°C)",
child: SizedBox(
height: 200,
child: _styledLineChart(
title: "电机温度对比",
lines: [_lineData(_leftTempHistory, const Color(0xFF3F51B5)), _lineData(_rightTempHistory, const Color(0xFF8BC34A))],
yAxisMax: 100,
leftTicks: const [0, 20, 40, 60, 80, 100],
),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2))],
),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"电机温度对比 (°C)",
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1677FF)),
),
const SizedBox(height: 16),
SizedBox(
height: 180,
child: _styledLineChart(
title: "电机温度对比",
lines: [_lineData(_leftTempHistory, const Color(0xFF3F51B5)), _lineData(_rightTempHistory, const Color(0xFF8BC34A))],
yAxisMax: 100,
leftTicks: const [0, 20, 40, 60, 80, 100],
),
),
],
),
),
_gap(),
// 航向角仪表盘
_chartCard(
title: "航向角 (°)",
child: _compassGauge(yaw: status.yaw),
// 航向角仪表盘(标题字体缩小+尺寸缩小)
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2))],
),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"航向角 (°)",
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1677FF)),
),
const SizedBox(height: 16),
_compassGauge(yaw: status.yaw),
],
),
),
_gap(),
],
@@ -580,7 +596,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
children: [
const Padding(
padding: EdgeInsets.symmetric(vertical: 12),
child: Text("电机参数", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
child: Text("电机参数", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), // 标题字体缩小
),
Table(
border: TableBorder.all(color: const Color(0xFFE5E5E5)),
@@ -631,7 +647,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
children: [
const Padding(
padding: EdgeInsets.symmetric(vertical: 12),
child: Text("其他参数", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
child: Text("其他参数", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), // 标题字体缩小
),
Table(
border: TableBorder.all(color: const Color(0xFFE5E5E5)),
@@ -831,7 +847,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
}
}
// ====================== 带动画的圆形仪表盘 Widget ======================
// ====================== 带动画的圆形仪表盘 Widget(尺寸缩小:原220→180,绘制尺寸280→240) ======================
class AnimatedCircularGauge extends StatefulWidget {
final double value;
final double maxValue;
@@ -852,64 +868,56 @@ class _AnimatedCircularGaugeState extends State<AnimatedCircularGauge> with Sing
@override
void initState() {
super.initState();
// 初始化动画控制器,时长800ms(可调整)
_animationController = AnimationController(vsync: this, duration: const Duration(milliseconds: 800));
// 初始值动画
_previousValue = widget.value;
_valueAnimation =
Tween<double>(begin: 0, end: widget.value).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeOutCubic, // 缓出曲线,动画更自然
),
)..addListener(() {
setState(() {}); // 动画值变化时刷新UI
});
_valueAnimation = Tween<double>(begin: 0, end: widget.value).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeOutCubic))
..addListener(() {
setState(() {});
});
_animationController.forward();
}
@override
void didUpdateWidget(covariant AnimatedCircularGauge oldWidget) {
super.didUpdateWidget(oldWidget);
// 数值变化时触发新的动画
if (widget.value != oldWidget.value) {
_previousValue = _valueAnimation.value; // 从当前值开始动画
_previousValue = _valueAnimation.value;
_valueAnimation =
Tween<double>(begin: _previousValue, end: widget.value).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeOutCubic))
..addListener(() {
setState(() {});
});
_animationController.reset(); // 重置动画
_animationController.forward(); // 执行动画
_animationController.reset();
_animationController.forward();
}
}
@override
void dispose() {
_animationController.dispose(); // 释放动画控制器
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 220,
height: 180, // 原220→180
child: Stack(
alignment: Alignment.center,
children: [
CustomPaint(
painter: CircularGaugePainter(
value: _valueAnimation.value, // 使用动画插值后的数值
maxValue: widget.maxValue,
majorTicks: widget.majorTicks,
),
size: const Size(280, 280),
painter: CircularGaugePainter(value: _valueAnimation.value, maxValue: widget.maxValue, majorTicks: widget.majorTicks),
size: const Size(240, 240), // 原280→240
),
Positioned(
bottom: 20,
child: Text(
"${_valueAnimation.value.toStringAsFixed(2)} ${widget.unit}",
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500, color: Colors.grey),
style: const TextStyle(
fontSize: 18, // 原20→18
fontWeight: FontWeight.w500,
color: Colors.grey,
),
),
),
],
@@ -918,22 +926,20 @@ class _AnimatedCircularGaugeState extends State<AnimatedCircularGauge> with Sing
}
}
// ====================== 圆形仪表盘绘制器(带动画支持) ======================
// ====================== 圆形仪表盘绘制器(修复widget关键字错误) ======================
// ====================== 圆形仪表盘绘制器(尺寸同步缩小) ======================
class CircularGaugePainter extends CustomPainter {
final double value;
final double maxValue;
final List<double> majorTicks; // 直接通过成员变量获取,而非widget
final List<double> majorTicks;
CircularGaugePainter({required this.value, required this.maxValue, required this.majorTicks});
@override
void paint(Canvas canvas, Size size) {
// 画布中心和半径(留出边距)
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 2 - 20;
// 1. 绘制背景圆弧(浅蓝底色,270°范围:从135°到405°,对应左下到右下的半圆)
// 背景圆弧
final bgArcPaint = Paint()
..color = const Color(0xFFE3F2FD)
..style = PaintingStyle.stroke
@@ -942,8 +948,8 @@ 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);
// 2. 绘制进度圆弧(浅蓝色,根据动画后的数值比例绘制)
final progressRatio = math.min(value / maxValue, 1.0); // 限制最大为1
// 进度圆弧
final progressRatio = math.min(value / maxValue, 1.0);
final progressPaint = Paint()
..color = const Color(0xFFBBDEFB)
..style = PaintingStyle.stroke
@@ -951,42 +957,38 @@ class CircularGaugePainter extends CustomPainter {
..strokeCap = StrokeCap.round;
canvas.drawArc(bgRect, 135 * math.pi / 180, 270 * math.pi / 180 * progressRatio, false, progressPaint);
// 3. 绘制刻度(主刻度+副刻度)
// 刻度
final tickPaint = Paint()..color = Colors.grey.shade400;
final majorTickPaint = Paint()
..color = Colors.grey.shade600
..strokeWidth = 2;
// 总刻度数(每1°一个副刻度,每10°一个主刻度)
const totalTicks = 270;
for (int i = 0; i < totalTicks; i++) {
// 计算刻度角度(从135°开始,到405°结束)
final angle = 135 * math.pi / 180 + (i * math.pi * 270 / 180) / totalTicks;
// 刻度起始和结束位置
final tickStart = center + Offset(math.cos(angle), math.sin(angle)) * (radius - 4);
final tickEnd = center + Offset(math.cos(angle), math.sin(angle)) * radius;
// 副刻度(短刻度)
tickPaint.strokeWidth = 1;
canvas.drawLine(tickStart, tickEnd, tickPaint);
// 主刻度(长刻度,匹配majorTicks的数值)
final tickValue = (i / totalTicks) * maxValue;
// 修复:直接使用this.majorTicks(去掉widget.)
if (majorTicks.any((tick) => (tickValue - tick).abs() < 0.1)) {
final majorTickStart = center + Offset(math.cos(angle), math.sin(angle)) * (radius - 8);
canvas.drawLine(majorTickStart, tickEnd, majorTickPaint);
// 绘制主刻度数值
final textValue = tickValue.toStringAsFixed(0);
final textPainter = TextPainter(
text: TextSpan(
text: textValue,
style: const TextStyle(fontSize: 14, color: Colors.grey, fontWeight: FontWeight.w500),
style: const TextStyle(
fontSize: 12, // 原14→12,刻度数值缩小
color: Colors.grey,
fontWeight: FontWeight.w500,
),
),
textDirection: TextDirection.ltr,
)..layout();
// 数值位置(在主刻度外侧)
final textOffset = center + Offset(math.cos(angle), math.sin(angle)) * (radius - 20);
final textX = textOffset.dx - textPainter.width / 2;
final textY = textOffset.dy - textPainter.height / 2;
@@ -994,38 +996,30 @@ class CircularGaugePainter extends CustomPainter {
}
}
// 4. 计算指针角度(使用动画后的数值)
// 指针角度
final needleAngle = 135 * math.pi / 180 + 270 * math.pi / 180 * progressRatio;
// 5. 绘制三角形指针(细尖角指向刻度,宽底边在中心)
// 三角形指针
final needlePaint = Paint()
..color = Colors.grey.shade600
..style = PaintingStyle.fill;
// 调整三角形顶点和底边位置:细尖角指向刻度,宽底边在中心
final needlePath = Path()
// 1. 移动画笔到指针的细尖角(指向刻度的一端)
..moveTo(center.dx + (radius - 10) * math.cos(needleAngle), center.dy + (radius - 10) * math.sin(needleAngle))
// 2. 绘制第一条边:从尖角到中心左侧的底边端点
..lineTo(
center.dx + 8 * math.cos(needleAngle - math.pi / 2), // 中心左侧8px
center.dy + 8 * math.sin(needleAngle - math.pi / 2),
center.dx + 6 * math.cos(needleAngle - math.pi / 2), // 原8→6,指针底边宽度缩小
center.dy + 6 * math.sin(needleAngle - math.pi / 2),
)
// 3. 绘制第二条边:从中心左侧端点到中心右侧的底边端点
..lineTo(
center.dx + 8 * math.cos(needleAngle + math.pi / 2), // 中心右侧8px
center.dy + 8 * math.sin(needleAngle + math.pi / 2),
)
// 4. 闭合路径:回到尖角,形成三角形
..lineTo(center.dx + 6 * math.cos(needleAngle + math.pi / 2), center.dy + 6 * math.sin(needleAngle + math.pi / 2))
..close();
canvas.drawPath(needlePath, needlePaint);
// 6. 绘制指针中心圆点(增强视觉效果)
// 中心圆点
final centerDotPaint = Paint()
..color = Colors.white
..style = PaintingStyle.fill;
canvas.drawCircle(center, 4, centerDotPaint);
canvas.drawCircle(center, 3, centerDotPaint); // 原4→3,圆点缩小
}
@override
@@ -1034,7 +1028,7 @@ class CircularGaugePainter extends CustomPainter {
}
}
// ====================== 罗盘式航向角仪表盘绘制器 ======================
// ====================== 罗盘式航向角仪表盘绘制器(尺寸缩小) ======================
class CompassGaugePainter extends CustomPainter {
final double yaw;
@@ -1049,8 +1043,7 @@ class CompassGaugePainter extends CustomPainter {
final outerPaint = Paint()
..color = const Color(0xFF1677FF)
..style = PaintingStyle.stroke
..strokeWidth = 12;
..strokeWidth = 10; // 原12→10,外圈宽度缩小
canvas.drawCircle(center, radius, outerPaint);
// 刻度
@@ -1066,13 +1059,11 @@ class CompassGaugePainter extends CustomPainter {
final y2 = center.dy + radius * math.sin(angle);
canvas.drawLine(Offset(x1, y1), Offset(x2, y2), tickPaint);
// 主刻度(45°间隔)
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
@@ -1086,7 +1077,7 @@ class CompassGaugePainter extends CustomPainter {
final textPainter = TextPainter(
text: TextSpan(
text: direction,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black), // 原18→16
),
textDirection: TextDirection.ltr,
)..layout();
@@ -1095,12 +1086,11 @@ class CompassGaugePainter extends CustomPainter {
textPainter.paint(canvas, Offset(textX, textY));
}
// 角度值
final angleValue = (i * 10).toString();
final textPainter = TextPainter(
text: TextSpan(
text: angleValue,
style: const TextStyle(fontSize: 14, color: Colors.black54),
style: const TextStyle(fontSize: 12, color: Colors.black54), // 原14→12
),
textDirection: TextDirection.ltr,
)..layout();