仪表盘动画实现

This commit is contained in:
mmc
2026-03-04 13:51:27 +08:00
parent ab9e69705d
commit cd6b9a59ff

View File

@@ -6,6 +6,7 @@ import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
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';
@@ -142,7 +143,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
);
}
// ====================== 圆形仪表盘(匹配目标样式) ======================
// ====================== 带动画的圆形仪表盘(核心修改) ======================
Widget _circularGauge({
required double value,
required double maxValue,
@@ -150,25 +151,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
required String label,
List<double> majorTicks = const [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
}) {
return SizedBox(
height: 220,
child: Stack(
alignment: Alignment.center,
children: [
CustomPaint(
painter: CircularGaugePainter(value: value, maxValue: maxValue, majorTicks: majorTicks),
size: const Size(280, 280),
),
Positioned(
bottom: 20,
child: Text(
"${value.toStringAsFixed(2)} $unit",
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500, color: Colors.grey),
),
),
],
),
);
return AnimatedCircularGauge(value: value, maxValue: maxValue, unit: unit, majorTicks: majorTicks);
}
// ====================== 罗盘式航向角仪表盘 ======================
@@ -848,11 +831,99 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
}
}
// ====================== 圆形仪表盘绘制器(修正指针样式) ======================
// ====================== 带动画的圆形仪表盘 Widget ======================
class AnimatedCircularGauge extends StatefulWidget {
final double value;
final double maxValue;
final String unit;
final List<double> majorTicks;
const AnimatedCircularGauge({super.key, required this.value, required this.maxValue, required this.unit, required this.majorTicks});
@override
State<AnimatedCircularGauge> createState() => _AnimatedCircularGaugeState();
}
class _AnimatedCircularGaugeState extends State<AnimatedCircularGauge> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _valueAnimation;
double _previousValue = 0.0;
@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
});
_animationController.forward();
}
@override
void didUpdateWidget(covariant AnimatedCircularGauge oldWidget) {
super.didUpdateWidget(oldWidget);
// 数值变化时触发新的动画
if (widget.value != oldWidget.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(); // 执行动画
}
}
@override
void dispose() {
_animationController.dispose(); // 释放动画控制器
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 220,
child: Stack(
alignment: Alignment.center,
children: [
CustomPaint(
painter: CircularGaugePainter(
value: _valueAnimation.value, // 使用动画插值后的数值
maxValue: widget.maxValue,
majorTicks: widget.majorTicks,
),
size: const Size(280, 280),
),
Positioned(
bottom: 20,
child: Text(
"${_valueAnimation.value.toStringAsFixed(2)} ${widget.unit}",
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500, color: Colors.grey),
),
),
],
),
);
}
}
// ====================== 圆形仪表盘绘制器(带动画支持) ======================
// ====================== 圆形仪表盘绘制器(修复widget关键字错误) ======================
class CircularGaugePainter extends CustomPainter {
final double value;
final double maxValue;
final List<double> majorTicks;
final List<double> majorTicks; // 直接通过成员变量获取,而非widget
CircularGaugePainter({required this.value, required this.maxValue, required this.majorTicks});
@@ -871,7 +942,7 @@ 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. 绘制进度圆弧(浅蓝色,根据数值比例绘制)
// 2. 绘制进度圆弧(浅蓝色,根据动画后的数值比例绘制)
final progressRatio = math.min(value / maxValue, 1.0); // 限制最大为1
final progressPaint = Paint()
..color = const Color(0xFFBBDEFB)
@@ -901,6 +972,7 @@ class CircularGaugePainter extends CustomPainter {
// 主刻度(长刻度,匹配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);
@@ -922,7 +994,7 @@ class CircularGaugePainter extends CustomPainter {
}
}
// 4. 计算指针角度
// 4. 计算指针角度(使用动画后的数值)
final needleAngle = 135 * math.pi / 180 + 270 * math.pi / 180 * progressRatio;
// 5. 绘制三角形指针(细尖角指向刻度,宽底边在中心)
@@ -933,7 +1005,7 @@ class CircularGaugePainter extends CustomPainter {
// 调整三角形顶点和底边位置:细尖角指向刻度,宽底边在中心
final needlePath = Path()
// 1. 移动画笔到指针的细尖角(指向刻度的一端)
..moveTo(center.dx + (radius - 6) * math.cos(needleAngle), center.dy + (radius - 6) * math.sin(needleAngle))
..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
@@ -949,21 +1021,16 @@ class CircularGaugePainter extends CustomPainter {
canvas.drawPath(needlePath, needlePaint);
//// 6. 绘制指针中心圆点(可选,增强视觉效果)
//final centerDotPaint = Paint()
// ..color = Colors.white
// ..style = PaintingStyle.fill;
//canvas.drawCircle(center, 4, centerDotPaint);
// 内层小圆点增加层次感
final innerDotPaint = Paint()
// 6. 绘制指针中心圆点(增强视觉效果)
final centerDotPaint = Paint()
..color = Colors.white
..style = PaintingStyle.fill;
canvas.drawCircle(center, 5, innerDotPaint);
canvas.drawCircle(center, 4, centerDotPaint);
}
@override
bool shouldRepaint(covariant CircularGaugePainter oldDelegate) {
return oldDelegate.value != value || oldDelegate.maxValue != maxValue;
return oldDelegate.value != value || oldDelegate.maxValue != maxValue || oldDelegate.majorTicks != majorTicks;
}
}