仪表盘动画实现
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user