Flutter 框架跨平台鸿蒙开发 - 简易测振仪:打造专业级振动监测应用
Flutter简易测振仪:打造专业级振动监测应用
项目概述
在工业监测、建筑检测、设备维护等领域,振动监测是一项重要的技术手段。本教程将带你使用Flutter开发一个功能完整的简易测振仪应用,实现实时振动监测、数据记录、波形显示等专业功能。
运行效果图



应用特色
- 实时监测:模拟加速度计数据,实时显示振动强度
- 可视化显示:动态圆形指示器和实时波形图
- 数据记录:完整的测量记录管理系统
- 统计分析:最大值、最小值、平均值等统计数据
- 专业界面:仿真专业测振设备的用户界面
技术架构
核心技术栈
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
项目结构
lib/
├── main.dart # 应用入口和主要逻辑
├── models/ # 数据模型(集成在main.dart中)
│ ├── vibration_data.dart # 振动数据模型
│ └── measurement_record.dart # 测量记录模型
├── pages/ # 页面组件(集成在main.dart中)
│ ├── vibration_meter_page.dart # 主测量页面
│ ├── records_list_page.dart # 记录列表页面
│ └── record_detail_page.dart # 记录详情页面
└── widgets/ # 自定义组件(集成在main.dart中)
├── waveform_painter.dart # 波形绘制器
└── detail_waveform_painter.dart # 详细波形绘制器
数据模型设计
振动数据模型(VibrationData)
振动数据模型是应用的核心数据结构,记录三轴加速度和振动强度:
class VibrationData {
final double x; // X轴加速度 (m/s²)
final double y; // Y轴加速度 (m/s²)
final double z; // Z轴加速度 (m/s²)
final double magnitude; // 振动强度 (m/s²)
final DateTime timestamp; // 时间戳
VibrationData({
required this.x,
required this.y,
required this.z,
required this.magnitude,
required this.timestamp,
});
}
振动强度通过三轴加速度计算得出:
final magnitude = sqrt(x * x + y * y + z * z);
测量记录模型(MeasurementRecord)
测量记录模型存储完整的测量会话数据:
class MeasurementRecord {
final String id; // 记录唯一标识
final DateTime startTime; // 开始时间
final DateTime endTime; // 结束时间
final double maxMagnitude; // 最大振动强度
final double avgMagnitude; // 平均振动强度
final double minMagnitude; // 最小振动强度
final List<VibrationData> dataPoints; // 数据点列表
final String location; // 测量位置
final String notes; // 备注信息
MeasurementRecord({
required this.id,
required this.startTime,
required this.endTime,
required this.maxMagnitude,
required this.avgMagnitude,
required this.minMagnitude,
required this.dataPoints,
required this.location,
required this.notes,
});
// 计算测量时长
Duration get duration => endTime.difference(startTime);
// 格式化时长显示
String get durationText {
final seconds = duration.inSeconds;
final minutes = seconds ~/ 60;
final remainingSeconds = seconds % 60;
return '${minutes}:${remainingSeconds.toString().padLeft(2, '0')}';
}
}
应用主体结构
应用入口
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: '简易测振仪',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const VibrationMeterPage(),
);
}
}
应用采用蓝色作为主题色,营造专业、科技的视觉效果。
主页面结构
主页面包含状态栏、振动显示、数据卡片、实时图表和控制按钮:
class VibrationMeterPage extends StatefulWidget {
const VibrationMeterPage({super.key});
State<VibrationMeterPage> createState() => _VibrationMeterPageState();
}
class _VibrationMeterPageState extends State<VibrationMeterPage>
with TickerProviderStateMixin {
// 测量状态
bool _isRecording = false;
bool _isPaused = false;
// 当前振动数据
VibrationData? _currentData;
// 历史数据和记录
final List<VibrationData> _dataHistory = [];
final List<MeasurementRecord> _records = [];
// 动画控制器
late AnimationController _pulseController;
late AnimationController _waveController;
// 测量参数
DateTime? _recordingStartTime;
int _recordingDuration = 0;
// 统计数据
double _maxMagnitude = 0.0;
double _minMagnitude = double.infinity;
double _avgMagnitude = 0.0;
// 设置参数
double _sensitivity = 1.0;
int _sampleRate = 50; // Hz
bool _showRealTimeGraph = true;
}
核心功能实现
振动数据模拟
由于移动设备的加速度计API在模拟器中不可用,我们使用数学函数模拟真实的振动数据:
void _simulateVibrationData() {
final random = Random();
// 模拟加速度计数据 (m/s²)
final baseNoise = 0.1;
final x = (random.nextDouble() - 0.5) * baseNoise * _sensitivity;
final y = (random.nextDouble() - 0.5) * baseNoise * _sensitivity;
final z = 9.8 + (random.nextDouble() - 0.5) * baseNoise * _sensitivity;
// 添加振动信号
final vibrationIntensity = _generateVibrationIntensity();
final vibX = x + vibrationIntensity * (random.nextDouble() - 0.5);
final vibY = y + vibrationIntensity * (random.nextDouble() - 0.5);
final vibZ = z + vibrationIntensity * (random.nextDouble() - 0.5);
final magnitude = sqrt(vibX * vibX + vibY * vibY + vibZ * vibZ);
final data = VibrationData(
x: vibX,
y: vibY,
z: vibZ,
magnitude: magnitude,
timestamp: DateTime.now(),
);
setState(() {
_currentData = data;
if (_isRecording && !_isPaused) {
_dataHistory.add(data);
_updateStatistics(magnitude);
}
});
}
double _generateVibrationIntensity() {
final random = Random();
final time = DateTime.now().millisecondsSinceEpoch / 1000.0;
// 基础振动 + 随机脉冲
double intensity = 0.5 * sin(time * 2 * pi * 0.5); // 0.5Hz 基础振动
intensity += 0.3 * sin(time * 2 * pi * 2.0); // 2Hz 振动
// 随机脉冲
if (random.nextDouble() < 0.05) {
intensity += random.nextDouble() * 3.0;
}
return intensity.abs();
}
状态栏设计
状态栏显示当前测量状态和时长:
Widget _buildStatusBar() {
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
color: _isRecording
? (_isPaused ? Colors.orange.withValues(alpha: 0.1) : Colors.red.withValues(alpha: 0.1))
: Colors.green.withValues(alpha: 0.1),
child: Row(
children: [
Icon(
_isRecording
? (_isPaused ? Icons.pause : Icons.fiber_manual_record)
: Icons.sensors,
color: _isRecording
? (_isPaused ? Colors.orange : Colors.red)
: Colors.green,
size: 16,
),
const SizedBox(width: 8),
Text(
_isRecording
? (_isPaused ? '测量已暂停' : '正在测量中...')
: '准备就绪',
style: TextStyle(
color: _isRecording
? (_isPaused ? Colors.orange : Colors.red)
: Colors.green,
fontWeight: FontWeight.w500,
),
),
const Spacer(),
if (_isRecording) ...[
Text(
_formatDuration(_recordingDuration),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
],
],
),
);
}
振动强度显示
核心的振动强度显示采用动态圆形指示器:
Widget _buildVibrationDisplay() {
final magnitude = _currentData?.magnitude ?? 0.0;
final normalizedMagnitude = (magnitude / 15.0).clamp(0.0, 1.0);
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
const Text(
'振动强度',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
AnimatedBuilder(
animation: _pulseController,
builder: (context, child) {
return Container(
width: 200,
height: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
_getVibrationColor(magnitude).withValues(alpha: 0.8),
_getVibrationColor(magnitude).withValues(alpha: 0.3),
_getVibrationColor(magnitude).withValues(alpha: 0.1),
],
stops: [
0.0,
0.7 + normalizedMagnitude * 0.3,
1.0,
],
),
boxShadow: [
BoxShadow(
color: _getVibrationColor(magnitude).withValues(alpha: 0.3),
blurRadius: 20 + normalizedMagnitude * 30,
spreadRadius: 5 + normalizedMagnitude * 10,
),
],
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
magnitude.toStringAsFixed(3),
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const Text(
'm/s²',
style: TextStyle(
fontSize: 14,
color: Colors.white70,
),
),
],
),
),
);
},
),
const SizedBox(height: 20),
Text(
_getVibrationLevel(magnitude),
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: _getVibrationColor(magnitude),
),
),
],
),
),
);
}
振动等级判断
根据振动强度判断振动等级和颜色:
Color _getVibrationColor(double magnitude) {
if (magnitude < 9.9) return Colors.green;
if (magnitude < 10.5) return Colors.yellow;
if (magnitude < 11.0) return Colors.orange;
return Colors.red;
}
String _getVibrationLevel(double magnitude) {
if (magnitude < 9.9) return '正常';
if (magnitude < 10.5) return '轻微振动';
if (magnitude < 11.0) return '中等振动';
return '强烈振动';
}
数据统计卡片
显示最大值、平均值、最小值的统计信息:
Widget _buildDataCards() {
return Row(
children: [
Expanded(
child: _buildDataCard(
'最大值',
'${_maxMagnitude.toStringAsFixed(3)} m/s²',
Icons.trending_up,
Colors.red,
),
),
const SizedBox(width: 8),
Expanded(
child: _buildDataCard(
'平均值',
'${_avgMagnitude.toStringAsFixed(3)} m/s²',
Icons.show_chart,
Colors.blue,
),
),
const SizedBox(width: 8),
Expanded(
child: _buildDataCard(
'最小值',
'${_minMagnitude == double.infinity ? 0.0 : _minMagnitude.toStringAsFixed(3)} m/s²',
Icons.trending_down,
Colors.green,
),
),
],
);
}
Widget _buildDataCard(String title, String value, IconData icon, Color color) {
return Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
children: [
Icon(icon, color: color, size: 24),
const SizedBox(height: 8),
Text(
title,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: color,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
实时波形图表
实时波形图表使用CustomPainter绘制振动波形:
Widget _buildRealTimeGraph() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'实时波形',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Container(
height: 150,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(8),
),
child: CustomPaint(
painter: WaveformPainter(_dataHistory.take(100).toList()),
),
),
],
),
),
);
}
波形绘制器实现
WaveformPainter类负责绘制实时振动波形:
class WaveformPainter extends CustomPainter {
final List<VibrationData> data;
WaveformPainter(this.data);
void paint(Canvas canvas, Size size) {
if (data.isEmpty) return;
final paint = Paint()
..color = Colors.green
..strokeWidth = 2
..style = PaintingStyle.stroke;
final path = Path();
for (int i = 0; i < data.length; i++) {
final x = (i / data.length) * size.width;
final normalizedMagnitude = ((data[i].magnitude - 9.8) / 2.0 + 0.5).clamp(0.0, 1.0);
final y = size.height - (normalizedMagnitude * size.height);
if (i == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
canvas.drawPath(path, paint);
// 绘制中心线
final centerLinePaint = Paint()
..color = Colors.grey.withValues(alpha: 0.5)
..strokeWidth = 1;
canvas.drawLine(
Offset(0, size.height / 2),
Offset(size.width, size.height / 2),
centerLinePaint,
);
}
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
测量控制功能
开始测量
void _startRecording() {
setState(() {
_isRecording = true;
_isPaused = false;
_recordingStartTime = DateTime.now();
_recordingDuration = 0;
_dataHistory.clear();
_maxMagnitude = 0.0;
_minMagnitude = double.infinity;
_avgMagnitude = 0.0;
});
_recordingTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
_recordingDuration++;
});
});
}
暂停和继续
void _pauseRecording() {
setState(() {
_isPaused = !_isPaused;
});
}
停止测量并保存记录
void _stopRecording() {
if (_recordingStartTime != null && _dataHistory.isNotEmpty) {
final record = MeasurementRecord(
id: 'record_${DateTime.now().millisecondsSinceEpoch}',
startTime: _recordingStartTime!,
endTime: DateTime.now(),
maxMagnitude: _maxMagnitude,
avgMagnitude: _avgMagnitude,
minMagnitude: _minMagnitude,
dataPoints: List.from(_dataHistory),
location: '未知位置',
notes: '',
);
setState(() {
_records.insert(0, record);
_isRecording = false;
_isPaused = false;
_recordingDuration = 0;
});
_recordingTimer?.cancel();
_showRecordSavedDialog(record);
}
}
控制按钮设计
Widget _buildControlButtons() {
return Column(
children: [
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _isRecording ? null : _startRecording,
icon: const Icon(Icons.play_arrow),
label: const Text('开始测量'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton.icon(
onPressed: _isRecording ? _pauseRecording : null,
icon: Icon(_isPaused ? Icons.play_arrow : Icons.pause),
label: Text(_isPaused ? '继续' : '暂停'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
),
],
),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: _isRecording ? _stopRecording : null,
icon: const Icon(Icons.stop),
label: const Text('停止测量'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
),
],
);
}
设置功能
设置对话框
void _showSettings() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('设置'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: const Text('灵敏度'),
subtitle: Slider(
value: _sensitivity,
min: 0.1,
max: 5.0,
divisions: 49,
label: _sensitivity.toStringAsFixed(1),
onChanged: (value) {
setState(() {
_sensitivity = value;
});
},
),
),
ListTile(
title: const Text('采样率 (Hz)'),
subtitle: Slider(
value: _sampleRate.toDouble(),
min: 10,
max: 100,
divisions: 18,
label: _sampleRate.toString(),
onChanged: (value) {
setState(() {
_sampleRate = value.toInt();
});
_measurementTimer?.cancel();
_startMeasurement();
},
),
),
SwitchListTile(
title: const Text('显示实时图表'),
value: _showRealTimeGraph,
onChanged: (value) {
setState(() {
_showRealTimeGraph = value;
});
},
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('确定'),
),
],
),
);
}
参数说明
- 灵敏度:调整振动信号的放大倍数,范围0.1-5.0
- 采样率:数据采集频率,范围10-100Hz
- 实时图表:控制是否显示实时波形图
记录管理功能
记录列表页面
class RecordsListPage extends StatelessWidget {
final List<MeasurementRecord> records;
const RecordsListPage({super.key, required this.records});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('测量记录'),
),
body: records.isEmpty
? const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.history, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text('暂无测量记录', style: TextStyle(color: Colors.grey)),
],
),
)
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: records.length,
itemBuilder: (context, index) {
final record = records[index];
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.blue.withValues(alpha: 0.1),
child: const Icon(Icons.analytics, color: Colors.blue),
),
title: Text(
'${_formatDateTime(record.startTime)}',
style: const TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('时长: ${record.durationText}'),
Text('最大振幅: ${record.maxMagnitude.toStringAsFixed(3)} m/s²'),
if (record.location.isNotEmpty)
Text('位置: ${record.location}'),
],
),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RecordDetailPage(record: record),
),
);
},
),
);
},
),
);
}
}
记录详情页面
记录详情页面展示完整的测量数据和分析结果:
class RecordDetailPage extends StatelessWidget {
final MeasurementRecord record;
const RecordDetailPage({super.key, required this.record});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('测量详情'),
actions: [
IconButton(
onPressed: () => _shareRecord(context),
icon: const Icon(Icons.share),
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoCard(),
const SizedBox(height: 16),
_buildStatisticsCard(),
const SizedBox(height: 16),
_buildGraphCard(),
],
),
),
);
}
Widget _buildInfoCard() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'基本信息',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
_buildInfoRow('开始时间', _formatDateTime(record.startTime)),
_buildInfoRow('结束时间', _formatDateTime(record.endTime)),
_buildInfoRow('测量时长', record.durationText),
_buildInfoRow('数据点数', '${record.dataPoints.length}'),
if (record.location.isNotEmpty)
_buildInfoRow('测量位置', record.location),
if (record.notes.isNotEmpty)
_buildInfoRow('备注', record.notes),
],
),
),
);
}
Widget _buildStatisticsCard() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'统计数据',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildStatItem(
'最大值',
'${record.maxMagnitude.toStringAsFixed(3)} m/s²',
Colors.red,
),
),
Expanded(
child: _buildStatItem(
'平均值',
'${record.avgMagnitude.toStringAsFixed(3)} m/s²',
Colors.blue,
),
),
Expanded(
child: _buildStatItem(
'最小值',
'${record.minMagnitude.toStringAsFixed(3)} m/s²',
Colors.green,
),
),
],
),
],
),
),
);
}
}
详细波形图表
详情页面使用更精细的波形绘制器:
class DetailWaveformPainter extends CustomPainter {
final List<VibrationData> data;
DetailWaveformPainter(this.data);
void paint(Canvas canvas, Size size) {
if (data.isEmpty) return;
final paint = Paint()
..color = Colors.cyan
..strokeWidth = 1.5
..style = PaintingStyle.stroke;
final path = Path();
// 找到数据范围
final minMag = data.map((d) => d.magnitude).reduce(min);
final maxMag = data.map((d) => d.magnitude).reduce(max);
final range = maxMag - minMag;
for (int i = 0; i < data.length; i++) {
final x = (i / (data.length - 1)) * size.width;
final normalizedY = range > 0 ? (data[i].magnitude - minMag) / range : 0.5;
final y = size.height - (normalizedY * size.height);
if (i == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
canvas.drawPath(path, paint);
// 绘制网格线
final gridPaint = Paint()
..color = Colors.grey.withValues(alpha: 0.3)
..strokeWidth = 0.5;
// 水平网格线
for (int i = 1; i < 5; i++) {
final y = (i / 5) * size.height;
canvas.drawLine(Offset(0, y), Offset(size.width, y), gridPaint);
}
// 垂直网格线
for (int i = 1; i < 10; i++) {
final x = (i / 10) * size.width;
canvas.drawLine(Offset(x, 0), Offset(x, size.height), gridPaint);
}
}
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
动画效果实现
脉冲动画
振动指示器使用脉冲动画增强视觉效果:
void _initializeAnimations() {
_pulseController = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
)..repeat(reverse: true);
_waveController = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this,
)..repeat();
}
动画应用
AnimatedBuilder(
animation: _pulseController,
builder: (context, child) {
return Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: _getVibrationColor(magnitude).withValues(alpha: 0.3),
blurRadius: 20 + normalizedMagnitude * 30,
spreadRadius: 5 + normalizedMagnitude * 10,
),
],
),
child: // 振动指示器内容
);
},
),
数据处理和统计
统计数据更新
void _updateStatistics(double magnitude) {
if (magnitude > _maxMagnitude) {
_maxMagnitude = magnitude;
}
if (magnitude < _minMagnitude) {
_minMagnitude = magnitude;
}
// 计算平均值
if (_dataHistory.isNotEmpty) {
final sum = _dataHistory.fold<double>(0, (sum, data) => sum + data.magnitude);
_avgMagnitude = sum / _dataHistory.length;
}
}
数据采样控制
void _startMeasurement() {
_measurementTimer = Timer.periodic(
Duration(milliseconds: 1000 ~/ _sampleRate),
(timer) => _simulateVibrationData(),
);
}
用户界面优化
响应式设计
应用采用响应式设计,适配不同屏幕尺寸:
// 振动显示器自适应大小
Container(
width: 200,
height: 200,
// 在小屏幕上可以调整为更小的尺寸
)
// 数据卡片使用Expanded平均分布
Row(
children: [
Expanded(child: _buildDataCard(...)),
const SizedBox(width: 8),
Expanded(child: _buildDataCard(...)),
const SizedBox(width: 8),
Expanded(child: _buildDataCard(...)),
],
)
颜色主题
应用使用一致的颜色主题:
// 主题色
Colors.blue - 主要UI元素
Colors.green - 正常状态
Colors.yellow - 警告状态
Colors.orange - 中等警告
Colors.red - 危险状态
// 背景色
Colors.blue.withValues(alpha: 0.1) - 浅色背景
Colors.black - 图表背景
Colors.grey - 辅助信息
图标使用
应用使用Material Design图标:
Icons.sensors - 传感器状态
Icons.fiber_manual_record - 录制状态
Icons.pause - 暂停状态
Icons.play_arrow - 播放/开始
Icons.stop - 停止
Icons.trending_up - 上升趋势
Icons.trending_down - 下降趋势
Icons.show_chart - 图表
Icons.analytics - 分析
Icons.history - 历史记录
Icons.settings - 设置
Icons.share - 分享
性能优化
数据管理
// 限制历史数据数量,避免内存溢出
if (_dataHistory.length > 1000) {
_dataHistory.removeAt(0);
}
// 实时图表只显示最近100个数据点
CustomPaint(
painter: WaveformPainter(_dataHistory.take(100).toList()),
)
动画优化
// 使用SingleTickerProviderStateMixin优化动画性能
class _VibrationMeterPageState extends State<VibrationMeterPage>
with TickerProviderStateMixin {
// 及时释放动画控制器
void dispose() {
_measurementTimer?.cancel();
_recordingTimer?.cancel();
_pulseController.dispose();
_waveController.dispose();
super.dispose();
}
}
绘制优化
// CustomPainter中的优化
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// 实时波形需要重绘
return true; // 对于WaveformPainter
// 静态波形不需要重绘
return false; // 对于DetailWaveformPainter
}
扩展功能建议
真实传感器集成
在真实设备上可以集成加速度计传感器:
// 添加依赖
dependencies:
sensors_plus: ^4.0.2
// 使用真实传感器数据
import 'package:sensors_plus/sensors_plus.dart';
void _startRealSensorMeasurement() {
accelerometerEvents.listen((AccelerometerEvent event) {
final magnitude = sqrt(event.x * event.x + event.y * event.y + event.z * event.z);
final data = VibrationData(
x: event.x,
y: event.y,
z: event.z,
magnitude: magnitude,
timestamp: DateTime.now(),
);
_processVibrationData(data);
});
}
数据导出功能
// 添加依赖
dependencies:
path_provider: ^2.1.1
share_plus: ^7.2.1
// 导出CSV格式数据
Future<void> _exportToCSV(MeasurementRecord record) async {
final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/vibration_${record.id}.csv');
final csvContent = StringBuffer();
csvContent.writeln('Timestamp,X,Y,Z,Magnitude');
for (final data in record.dataPoints) {
csvContent.writeln(
'${data.timestamp.toIso8601String()},'
'${data.x},${data.y},${data.z},${data.magnitude}'
);
}
await file.writeAsString(csvContent.toString());
// 分享文件
await Share.shareXFiles([XFile(file.path)]);
}
频谱分析
// 添加依赖
dependencies:
fftea: ^1.0.0
// FFT频谱分析
List<double> _performFFT(List<double> timeData) {
final fft = FFT(timeData.length);
final complexData = timeData.map((x) => Complex(x, 0)).toList();
final result = fft.run(complexData);
return result.map((c) => c.magnitude).toList();
}
阈值报警
// 添加振动阈值监测
class VibrationAlarm {
static const double WARNING_THRESHOLD = 10.5;
static const double DANGER_THRESHOLD = 12.0;
static void checkThreshold(double magnitude) {
if (magnitude > DANGER_THRESHOLD) {
_showDangerAlert();
} else if (magnitude > WARNING_THRESHOLD) {
_showWarningAlert();
}
}
static void _showDangerAlert() {
// 显示危险警报
// 可以添加声音、振动等提醒
}
static void _showWarningAlert() {
// 显示警告提醒
}
}
总结
本教程完整展示了如何使用Flutter开发一个功能丰富的简易测振仪应用。应用包含了实时振动监测、数据记录、波形显示、统计分析等专业功能,采用了现代化的UI设计和流畅的用户体验。
主要特点
- 专业功能:完整的振动监测和数据分析功能
- 直观界面:仿真专业设备的用户界面设计
- 实时显示:动态振动指示器和实时波形图
- 数据管理:完整的测量记录管理系统
- 可扩展性:便于集成真实传感器和高级功能
技术亮点
- 自定义绘制:使用CustomPainter绘制专业波形图
- 动画效果:脉冲动画增强视觉反馈
- 数据模拟:数学函数模拟真实振动信号
- 状态管理:完整的测量状态控制
- 性能优化:合理的数据管理和绘制优化
学习收获
通过本教程,你将掌握:
- Flutter自定义绘制技术
- 动画控制和视觉效果
- 数据采集和处理方法
- 专业应用界面设计
- 性能优化技巧
这个简易测振仪应用为你提供了一个完整的Flutter专业应用开发实践案例,可以作为学习Flutter高级功能的重要参考,也可以作为实际工程应用的基础框架。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐




所有评论(0)