Flutter 与传感器桥接
故事开始:小陈的健身追踪应用
小陈正在开发一个健身追踪应用,需要利用手机的传感器来监测用户的运动状态、步数、方向等信息。他发现 Flutter 中的传感器功能比想象中要复杂。
"传感器数据涉及频率控制、数据滤波、传感器融合、功耗优化等多个方面,而且 Android 和 iOS 的传感器 API 差异很大。"小陈在开发笔记中写道。
第一章:传感器技术基础
1.1 传感器类型概述
现代移动设备配备了丰富的传感器:
运动传感器:
- 加速度计(Accelerometer):测量三轴加速度
- 陀螺仪(Gyroscope):测量三轴角速度
- 磁力计(Magnetometer):测量三轴磁场强度
- 重力传感器(Gravity):测量重力加速度
- 线性加速度计(Linear Acceleration):去除重力影响的加速度
姿态传感器:
- 方向传感器(Orientation):设备方向角度
- 旋转矢量传感器(Rotation Vector):设备旋转四元数
- 游戏旋转矢量传感器(Game Rotation Vector):游戏优化的旋转数据
环境传感器:
- 光线传感器(Light):环境光照强度
- 距离传感器(Proximity):物体距离检测
- 气压计(Pressure):大气压力
- 温度传感器(Temperature):环境温度
- 湿度传感器(Humidity):环境湿度
健康传感器:
- 心率传感器(Heart Rate):心率监测
- 步数计(Step Counter):步数统计
- 计步器(Step Detector):步数检测
1.2 传感器数据特性
数据精度:
- 低精度:适合一般应用
- 中精度:适合大多数应用
- 高精度:适合专业应用
采样频率:
- 游戏模式:高频率,低延迟
- UI 模式:中等频率,平衡性能
- 正常模式:标准频率,省电
数据滤波:
- 低通滤波:去除高频噪声
- 高通滤波:去除低频漂移
- 卡尔曼滤波:最优估计
1.3 Flutter 传感器开发生态
Flutter 中传感器开发主要有以下几种方案:
- sensors_plus - 官方推荐的传感器插件
- flutter_sensors - 轻量级传感器插件
- 自定义平台通道 - 完全自定义实现
第二章:环境搭建与基础配置
2.1 添加依赖
yaml
dependencies:
flutter:
sdk: flutter
sensors_plus: ^4.0.1
permission_handler: ^11.0.12.2 权限配置
Android 权限配置(android/app/src/main/AndroidManifest.xml)
xml
<!-- 身体传感器权限 -->
<uses-permission android:name="android.permission.BODY_SENSORS" />
<uses-permission android:name="android.permission.BODY_SENSORS_BACKGROUND" />
<!-- 位置权限(某些传感器需要) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 后台传感器权限 -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />iOS 权限配置(ios/Runner/Info.plist)
xml
<!-- 运动和健身权限 -->
<key>NSMotionUsageDescription</key>
<string>此应用需要运动权限来监测您的健身活动</string>
<!-- 位置权限 -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>此应用需要位置权限来提供更准确的传感器数据</string>
<!-- 后台位置权限 -->
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>此应用需要后台位置权限来持续监测传感器数据</string>2.3 权限管理实现
dart
import 'package:permission_handler/permission_handler.dart';
class SensorPermissionManager {
static Future<bool> requestMotionPermissions() async {
if (Platform.isAndroid) {
final motion = await Permission.sensors.request();
return motion.isGranted;
} else {
// iOS需要用户在设置中授权
return await Permission.sensors.isGranted;
}
}
static Future<bool> requestLocationPermissions() async {
final location = await Permission.location.request();
return location.isGranted;
}
static Future<bool> checkMotionPermissions() async {
return await Permission.sensors.isGranted;
}
static Future<bool> checkLocationPermissions() async {
return await Permission.location.isGranted;
}
static Future<void> openSettings() async {
await openAppSettings();
}
}第三章:运动传感器实现
3.1 加速度计管理器
dart
import 'package:sensors_plus/sensors_plus.dart';
class AccelerometerManager {
static final AccelerometerManager _instance = AccelerometerManager._internal();
factory AccelerometerManager() => _instance;
AccelerometerManager._internal();
final StreamController<AccelerometerData> _dataController =
StreamController<AccelerometerData>.broadcast();
bool _isListening = false;
double _frequency = SensorInterval.normalInterval;
Stream<AccelerometerData> get accelerometerStream => _dataController.stream;
bool get isListening => _isListening;
double get frequency => _frequency;
Future<void> startListening({double? frequency}) async {
if (_isListening) return;
final hasPermission = await SensorPermissionManager.checkMotionPermissions();
if (!hasPermission) {
final granted = await SensorPermissionManager.requestMotionPermissions();
if (!granted) {
throw SensorException('需要传感器权限');
}
}
if (frequency != null) {
_frequency = frequency;
accelerometerEvent.listen(_onAccelerometerEvent,
samplingPeriod: _frequency);
} else {
accelerometerEvent.listen(_onAccelerometerEvent);
}
_isListening = true;
}
void stopListening() {
if (!_isListening) return;
accelerometerEvent.cancel();
_isListening = false;
}
void _onAccelerometerEvent(AccelerometerEvent event) {
final data = AccelerometerData(
x: event.x,
y: event.y,
z: event.z,
timestamp: DateTime.now(),
);
_dataController.add(data);
}
void dispose() {
stopListening();
_dataController.close();
}
}
class AccelerometerData {
final double x;
final double y;
final double z;
final DateTime timestamp;
AccelerometerData({
required this.x,
required this.y,
required this.z,
required this.timestamp,
});
double get magnitude {
return sqrt(x * x + y * y + z * z);
}
double get pitch {
return atan2(y, sqrt(x * x + z * z)) * 180 / pi;
}
double get roll {
return atan2(-x, y) * 180 / pi;
}
Map<String, dynamic> toJson() {
return {
'x': x,
'y': y,
'z': z,
'timestamp': timestamp.toIso8601String(),
'magnitude': magnitude,
'pitch': pitch,
'roll': roll,
};
}
}3.2 陀螺仪管理器
dart
class GyroscopeManager {
static final GyroscopeManager _instance = GyroscopeManager._internal();
factory GyroscopeManager() => _instance;
GyroscopeManager._internal();
final StreamController<GyroscopeData> _dataController =
StreamController<GyroscopeData>.broadcast();
bool _isListening = false;
double _frequency = SensorInterval.normalInterval;
Stream<GyroscopeData> get gyroscopeStream => _dataController.stream;
bool get isListening => _isListening;
double get frequency => _frequency;
Future<void> startListening({double? frequency}) async {
if (_isListening) return;
final hasPermission = await SensorPermissionManager.checkMotionPermissions();
if (!hasPermission) {
final granted = await SensorPermissionManager.requestMotionPermissions();
if (!granted) {
throw SensorException('需要传感器权限');
}
}
if (frequency != null) {
_frequency = frequency;
gyroscopeEvent.listen(_onGyroscopeEvent,
samplingPeriod: _frequency);
} else {
gyroscopeEvent.listen(_onGyroscopeEvent);
}
_isListening = true;
}
void stopListening() {
if (!_isListening) return;
gyroscopeEvent.cancel();
_isListening = false;
}
void _onGyroscopeEvent(GyroscopeEvent event) {
final data = GyroscopeData(
x: event.x,
y: event.y,
z: event.z,
timestamp: DateTime.now(),
);
_dataController.add(data);
}
void dispose() {
stopListening();
_dataController.close();
}
}
class GyroscopeData {
final double x;
final double y;
final double z;
final DateTime timestamp;
GyroscopeData({
required this.x,
required this.y,
required this.z,
required this.timestamp,
});
double get magnitude {
return sqrt(x * x + y * y + z * z);
}
Map<String, dynamic> toJson() {
return {
'x': x,
'y': y,
'z': z,
'timestamp': timestamp.toIso8601String(),
'magnitude': magnitude,
};
}
}3.3 磁力计管理器
dart
class MagnetometerManager {
static final MagnetometerManager _instance = MagnetometerManager._internal();
factory MagnetometerManager() => _instance;
MagnetometerManager._internal();
final StreamController<MagnetometerData> _dataController =
StreamController<MagnetometerData>.broadcast();
bool _isListening = false;
double _frequency = SensorInterval.normalInterval;
Stream<MagnetometerData> get magnetometerStream => _dataController.stream;
bool get isListening => _isListening;
double get frequency => _frequency;
Future<void> startListening({double? frequency}) async {
if (_isListening) return;
final hasPermission = await SensorPermissionManager.checkMotionPermissions();
if (!hasPermission) {
final granted = await SensorPermissionManager.requestMotionPermissions();
if (!granted) {
throw SensorException('需要传感器权限');
}
}
if (frequency != null) {
_frequency = frequency;
magnetometerEvent.listen(_onMagnetometerEvent,
samplingPeriod: _frequency);
} else {
magnetometerEvent.listen(_onMagnetometerEvent);
}
_isListening = true;
}
void stopListening() {
if (!_isListening) return;
magnetometerEvent.cancel();
_isListening = false;
}
void _onMagnetometerEvent(MagnetometerEvent event) {
final data = MagnetometerData(
x: event.x,
y: event.y,
z: event.z,
timestamp: DateTime.now(),
);
_dataController.add(data);
}
void dispose() {
stopListening();
_dataController.close();
}
}
class MagnetometerData {
final double x;
final double y;
final double z;
final DateTime timestamp;
MagnetometerData({
required this.x,
required this.y,
required this.z,
required this.timestamp,
});
double get magnitude {
return sqrt(x * x + y * y + z * z);
}
double get heading {
return atan2(y, x) * 180 / pi;
}
Map<String, dynamic> toJson() {
return {
'x': x,
'y': y,
'z': z,
'timestamp': timestamp.toIso8601String(),
'magnitude': magnitude,
'heading': heading,
};
}
}第四章:传感器融合与姿态检测
4.1 传感器融合管理器
dart
class SensorFusionManager {
static final SensorFusionManager _instance = SensorFusionManager._internal();
factory SensorFusionManager() => _instance;
SensorFusionManager._internal();
final AccelerometerManager _accelerometerManager = AccelerometerManager();
final GyroscopeManager _gyroscopeManager = GyroscopeManager();
final MagnetometerManager _magnetometerManager = MagnetometerManager();
final StreamController<OrientationData> _orientationController =
StreamController<OrientationData>.broadcast();
final StreamController<MotionData> _motionController =
StreamController<MotionData>.broadcast();
bool _isListening = false;
OrientationData? _lastOrientation;
MotionData? _lastMotion;
Stream<OrientationData> get orientationStream => _orientationController.stream;
Stream<MotionData> get motionStream => _motionController.stream;
bool get isListening => _isListening;
Future<void> startListening() async {
if (_isListening) return;
final hasPermission = await SensorPermissionManager.checkMotionPermissions();
if (!hasPermission) {
final granted = await SensorPermissionManager.requestMotionPermissions();
if (!granted) {
throw SensorException('需要传感器权限');
}
}
// 启动所有传感器
await _accelerometerManager.startListening();
await _gyroscopeManager.startListening();
await _magnetometerManager.startListening();
// 监听传感器数据
_accelerometerManager.accelerometerStream.listen(_onAccelerometerData);
_gyroscopeManager.gyroscopeStream.listen(_onGyroscopeData);
_magnetometerManager.magnetometerStream.listen(_onMagnetometerData);
_isListening = true;
}
void stopListening() {
if (!_isListening) return;
_accelerometerManager.stopListening();
_gyroscopeManager.stopListening();
_magnetometerManager.stopListening();
_isListening = false;
}
void _onAccelerometerData(AccelerometerData data) {
_updateMotionData(data);
}
void _onGyroscopeData(GyroscopeData data) {
_updateOrientationData(data);
}
void _onMagnetometerData(MagnetometerData data) {
_updateOrientationData(data);
}
void _updateMotionData(AccelerometerData accelData) {
final motion = MotionData(
acceleration: accelData.magnitude,
timestamp: accelData.timestamp,
isMoving: accelData.magnitude > 12.0, // 运动阈值
shakeDetected: accelData.magnitude > 20.0, // 摇晃检测
);
_lastMotion = motion;
_motionController.add(motion);
}
void _updateOrientationData(dynamic sensorData) {
// 简化的方向计算
// 实际应用中应该使用更复杂的传感器融合算法
final orientation = OrientationData(
azimuth: 0.0, // 需要磁力计数据计算
pitch: 0.0, // 需要加速度计数据计算
roll: 0.0, // 需要加速度计数据计算
timestamp: DateTime.now(),
);
_lastOrientation = orientation;
_orientationController.add(orientation);
}
OrientationData? get currentOrientation => _lastOrientation;
MotionData? get currentMotion => _lastMotion;
void dispose() {
stopListening();
_orientationController.close();
_motionController.close();
}
}
class OrientationData {
final double azimuth; // 方位角
final double pitch; // 俯仰角
final double roll; // 滚转角
final DateTime timestamp;
OrientationData({
required this.azimuth,
required this.pitch,
required this.roll,
required this.timestamp,
});
String get orientationDescription {
if (pitch.abs() < 30) {
if (azimuth >= 315 || azimuth < 45) return '北';
if (azimuth >= 45 && azimuth < 135) return '东';
if (azimuth >= 135 && azimuth < 225) return '南';
if (azimuth >= 225 && azimuth < 315) return '西';
} else if (pitch > 30) {
return '倒置';
} else if (pitch < -30) {
return '竖直';
}
return '未知';
}
Map<String, dynamic> toJson() {
return {
'azimuth': azimuth,
'pitch': pitch,
'roll': roll,
'timestamp': timestamp.toIso8601String(),
'orientation': orientationDescription,
};
}
}
class MotionData {
final double acceleration;
final DateTime timestamp;
final bool isMoving;
final bool shakeDetected;
MotionData({
required this.acceleration,
required this.timestamp,
required this.isMoving,
required this.shakeDetected,
});
Map<String, dynamic> toJson() {
return {
'acceleration': acceleration,
'timestamp': timestamp.toIso8601String(),
'isMoving': isMoving,
'shakeDetected': shakeDetected,
};
}
}4.2 步数检测器
dart
class StepDetectorManager {
static final StepDetectorManager _instance = StepDetectorManager._internal();
factory StepDetectorManager() => _instance;
StepDetectorManager._internal();
final StreamController<StepData> _stepController =
StreamController<StepData>.broadcast();
bool _isListening = false;
int _totalSteps = 0;
DateTime? _lastStepTime;
List<double> _accelerationHistory = [];
Stream<StepData> get stepStream => _stepController.stream;
bool get isListening => _isListening;
int get totalSteps => _totalSteps;
Future<void> startListening() async {
if (_isListening) return;
final hasPermission = await SensorPermissionManager.checkMotionPermissions();
if (!hasPermission) {
final granted = await SensorPermissionManager.requestMotionPermissions();
if (!granted) {
throw SensorException('需要传感器权限');
}
}
// 使用加速度计检测步数
final accelerometerManager = AccelerometerManager();
await accelerometerManager.startListening(frequency: SensorInterval.uiInterval);
accelerometerManager.accelerometerStream.listen(_onAccelerometerData);
_isListening = true;
}
void stopListening() {
if (!_isListening) return;
_isListening = false;
}
void _onAccelerometerData(AccelerometerData data) {
_accelerationHistory.add(data.magnitude);
// 保持历史记录在合理范围内
if (_accelerationHistory.length > 50) {
_accelerationHistory.removeAt(0);
}
// 简单的步数检测算法
if (_detectStep(data)) {
_totalSteps++;
final stepData = StepData(
stepCount: _totalSteps,
timestamp: data.timestamp,
stepFrequency: _calculateStepFrequency(),
);
_stepController.add(stepData);
_lastStepTime = data.timestamp;
}
}
bool _detectStep(AccelerometerData data) {
// 基本步数检测逻辑
final now = data.timestamp;
// 检查时间间隔(避免重复检测)
if (_lastStepTime != null) {
final timeDiff = now.difference(_lastStepTime!).inMilliseconds;
if (timeDiff < 200) return false; // 最小步数间隔200ms
}
// 检查加速度阈值
if (data.magnitude < 10.0) return false;
// 检查加速度模式(简化版)
if (_accelerationHistory.length < 10) return false;
// 寻找加速度的峰值模式
final recent = _accelerationHistory.sublist(_accelerationHistory.length - 10);
final maxAccel = recent.reduce((a, b) => a > b ? a : b);
final minAccel = recent.reduce((a, b) => a < b ? a : b);
// 检测到明显的加速度变化
return (maxAccel - minAccel) > 8.0;
}
double _calculateStepFrequency() {
if (_lastStepTime == null) return 0.0;
final now = DateTime.now();
final timeDiff = now.difference(_lastStepTime!).inMilliseconds;
if (timeDiff == 0) return 0.0;
return 1000.0 / timeDiff; // 步数/秒
}
void resetSteps() {
_totalSteps = 0;
_lastStepTime = null;
_accelerationHistory.clear();
}
void dispose() {
stopListening();
_stepController.close();
}
}
class StepData {
final int stepCount;
final DateTime timestamp;
final double stepFrequency;
StepData({
required this.stepCount,
required this.timestamp,
required this.stepFrequency,
});
Map<String, dynamic> toJson() {
return {
'stepCount': stepCount,
'timestamp': timestamp.toIso8601String(),
'stepFrequency': stepFrequency,
};
}
}第五章:环境传感器实现
5.1 光线传感器管理器
dart
class LightSensorManager {
static final LightSensorManager _instance = LightSensorManager._internal();
factory LightSensorManager() => _instance;
LightSensorManager._internal();
final StreamController<LightData> _dataController =
StreamController<LightData>.broadcast();
bool _isListening = false;
Stream<LightData> get lightStream => _dataController.stream;
bool get isListening => _isListening;
Future<void> startListening() async {
if (_isListening) return;
final hasPermission = await SensorPermissionManager.checkMotionPermissions();
if (!hasPermission) {
final granted = await SensorPermissionManager.requestMotionPermissions();
if (!granted) {
throw SensorException('需要传感器权限');
}
}
// 使用光线传感器
userAccelerometerEvent.listen(_onLightEvent);
_isListening = true;
}
void stopListening() {
if (!_isListening) return;
userAccelerometerEvent.cancel();
_isListening = false;
}
void _onLightEvent(UserAccelerometerEvent event) {
// 注意:这里使用userAccelerometer作为示例
// 实际应该使用专门的光线传感器API
final lightLevel = _calculateLightLevel(event);
final data = LightData(
illuminance: lightLevel,
timestamp: DateTime.now(),
environmentType: _getEnvironmentType(lightLevel),
);
_dataController.add(data);
}
double _calculateLightLevel(UserAccelerometerEvent event) {
// 模拟光线传感器数据
// 实际应用中应该使用真实的光线传感器
return (event.x.abs() + event.y.abs() + event.z.abs()) * 100;
}
LightEnvironmentType _getEnvironmentType(double illuminance) {
if (illuminance < 10) {
return LightEnvironmentType.dark;
} else if (illuminance < 50) {
return LightEnvironmentType.dim;
} else if (illuminance < 200) {
return LightEnvironmentType.normal;
} else if (illuminance < 500) {
return LightEnvironmentType.bright;
} else {
return LightEnvironmentType.veryBright;
}
}
void dispose() {
stopListening();
_dataController.close();
}
}
class LightData {
final double illuminance; // 光照强度(lux)
final DateTime timestamp;
final LightEnvironmentType environmentType;
LightData({
required this.illuminance,
required this.timestamp,
required this.environmentType,
});
Map<String, dynamic> toJson() {
return {
'illuminance': illuminance,
'timestamp': timestamp.toIso8601String(),
'environmentType': environmentType.toString(),
};
}
}
enum LightEnvironmentType {
dark,
dim,
normal,
bright,
veryBright,
}5.2 距离传感器管理器
dart
class ProximitySensorManager {
static final ProximitySensorManager _instance = ProximitySensorManager._internal();
factory ProximitySensorManager() => _instance;
ProximitySensorManager._internal();
final StreamController<ProximityData> _dataController =
StreamController<ProximityData>.broadcast();
bool _isListening = false;
Stream<ProximityData> get proximityStream => _dataController.stream;
bool get isListening => _isListening;
Future<void> startListening() async {
if (_isListening) return;
final hasPermission = await SensorPermissionManager.checkMotionPermissions();
if (!hasPermission) {
final granted = await SensorPermissionManager.requestMotionPermissions();
if (!granted) {
throw SensorException('需要传感器权限');
}
}
// 使用距离传感器
userAccelerometerEvent.listen(_onProximityEvent);
_isListening = true;
}
void stopListening() {
if (!_isListening) return;
userAccelerometerEvent.cancel();
_isListening = false;
}
void _onProximityEvent(UserAccelerometerEvent event) {
// 注意:这里使用userAccelerometer作为示例
// 实际应用中应该使用专门的距离传感器API
final isNear = _detectProximity(event);
final data = ProximityData(
isNear: isNear,
timestamp: DateTime.now(),
distance: isNear ? 0.0 : 10.0, // 模拟距离值
);
_dataController.add(data);
}
bool _detectProximity(UserAccelerometerEvent event) {
// 模拟距离传感器检测
// 实际应用中应该使用真实的距离传感器
final totalAcceleration = event.x.abs() + event.y.abs() + event.z.abs();
return totalAcceleration > 15.0; // 阈值检测
}
void dispose() {
stopListening();
_dataController.close();
}
}
class ProximityData {
final bool isNear;
final DateTime timestamp;
final double distance; // 距离(cm)
ProximityData({
required this.isNear,
required this.timestamp,
required this.distance,
});
Map<String, dynamic> toJson() {
return {
'isNear': isNear,
'timestamp': timestamp.toIso8601String(),
'distance': distance,
};
}
}第六章:实际应用案例
6.1 健身追踪应用
dart
class FitnessTracker {
final SensorFusionManager _sensorFusion = SensorFusionManager();
final StepDetectorManager _stepDetector = StepDetectorManager();
final LightSensorManager _lightSensor = LightSensorManager();
final StreamController<FitnessData> _fitnessController =
StreamController<FitnessData>.broadcast();
bool _isTracking = false;
DateTime? _startTime;
int _lastStepCount = 0;
double _totalDistance = 0.0;
List<AccelerometerData> _motionHistory = [];
Stream<FitnessData> get fitnessStream => _fitnessController.stream;
bool get isTracking => _isTracking;
Future<void> startTracking() async {
if (_isTracking) return;
try {
await _sensorFusion.startListening();
await _stepDetector.startListening();
await _lightSensor.startListening();
_isTracking = true;
_startTime = DateTime.now();
_lastStepCount = _stepDetector.totalSteps;
_motionHistory.clear();
// 监听传感器数据
_sensorFusion.motionStream.listen(_onMotionData);
_stepDetector.stepStream.listen(_onStepData);
_lightSensor.lightStream.listen(_onLightData);
} catch (e) {
throw SensorException('开始追踪失败:${e.toString()}');
}
}
void stopTracking() {
if (!_isTracking) return;
_sensorFusion.stopListening();
_stepDetector.stopListening();
_lightSensor.stopListening();
_isTracking = false;
_startTime = null;
}
void _onMotionData(MotionData motionData) {
_motionHistory.add(AccelerometerData(
x: 0, y: 0, z: motionData.acceleration,
timestamp: motionData.timestamp,
));
// 保持历史记录在合理范围内
if (_motionHistory.length > 100) {
_motionHistory.removeAt(0);
}
_emitFitnessData();
}
void _onStepData(StepData stepData) {
final newSteps = stepData.stepCount - _lastStepCount;
if (newSteps > 0) {
// 估算步长(简化计算)
final stepLength = 0.7; // 平均步长70cm
_totalDistance += newSteps * stepLength;
_lastStepCount = stepData.stepCount;
}
_emitFitnessData();
}
void _onLightData(LightData lightData) {
_emitFitnessData();
}
void _emitFitnessData() {
if (!_isTracking || _startTime == null) return;
final now = DateTime.now();
final duration = now.difference(_startTime!);
final fitnessData = FitnessData(
steps: _stepDetector.totalSteps,
distance: _totalDistance,
duration: duration,
calories: _calculateCalories(),
speed: _calculateSpeed(),
timestamp: now,
isMoving: _sensorFusion.currentMotion?.isMoving ?? false,
environmentType: _lightSensor.currentEnvironmentType,
);
_fitnessController.add(fitnessData);
}
double _calculateCalories() {
// 简化的卡路里计算
// 实际应用中应该考虑体重、年龄、性别等因素
final minutes = _startTime != null
? DateTime.now().difference(_startTime!).inMinutes
: 0;
return minutes * 5.0; // 假设每分钟消耗5卡路里
}
double _calculateSpeed() {
if (_startTime == null) return 0.0;
final duration = DateTime.now().difference(_startTime!);
if (duration.inSeconds == 0) return 0.0;
return (_totalDistance / 100) / (duration.inSeconds / 60); // km/h
}
void resetTracking() {
_stepDetector.resetSteps();
_totalDistance = 0.0;
_motionHistory.clear();
_startTime = DateTime.now();
}
void dispose() {
stopTracking();
_fitnessController.close();
}
}
class FitnessData {
final int steps;
final double distance; // 米
final Duration duration;
final double calories;
final double speed; // km/h
final DateTime timestamp;
final bool isMoving;
final LightEnvironmentType environmentType;
FitnessData({
required this.steps,
required this.distance,
required this.duration,
required this.calories,
required this.speed,
required this.timestamp,
required this.isMoving,
required this.environmentType,
});
String get formattedDistance {
if (distance < 1000) {
return '${distance.toStringAsFixed(1)}m';
} else {
return '${(distance / 1000).toStringAsFixed(2)}km';
}
}
String get formattedDuration {
final hours = duration.inHours;
final minutes = duration.inMinutes % 60;
final seconds = duration.inSeconds % 60;
if (hours > 0) {
return '${hours.toString().padLeft(2, '0')}:'
'${minutes.toString().padLeft(2, '0')}:'
'${seconds.toString().padLeft(2, '0')}';
} else {
return '${minutes.toString().padLeft(2, '0')}:'
'${seconds.toString().padLeft(2, '0')}';
}
}
Map<String, dynamic> toJson() {
return {
'steps': steps,
'distance': distance,
'duration': duration.inSeconds,
'calories': calories,
'speed': speed,
'timestamp': timestamp.toIso8601String(),
'isMoving': isMoving,
'environmentType': environmentType.toString(),
'formattedDistance': formattedDistance,
'formattedDuration': formattedDuration,
};
}
}6.2 传感器数据可视化
dart
class SensorVisualizationPage extends StatefulWidget {
@override
_SensorVisualizationPageState createState() => _SensorVisualizationPageState();
}
class _SensorVisualizationPageState extends State<SensorVisualizationPage> {
final AccelerometerManager _accelerometerManager = AccelerometerManager();
final GyroscopeManager _gyroscopeManager = GyroscopeManager();
final MagnetometerManager _magnetometerManager = MagnetometerManager();
List<AccelerometerData> _accelerometerData = [];
List<GyroscopeData> _gyroscopeData = [];
List<MagnetometerData> _magnetometerData = [];
@override
void initState() {
super.initState();
_startSensorListening();
}
@override
void dispose() {
_stopSensorListening();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('传感器可视化'),
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Column(
children: [
_buildAccelerometerCard(),
SizedBox(height: 16),
_buildGyroscopeCard(),
SizedBox(height: 16),
_buildMagnetometerCard(),
SizedBox(height: 16),
_build3DVisualization(),
],
),
),
);
}
Widget _buildAccelerometerCard() {
return Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'加速度计',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16),
_buildDataChart(
_accelerometerData.map((data) => data.magnitude).toList(),
'加速度 (m/s²)',
Colors.blue,
),
SizedBox(height: 16),
_buildCurrentValues(
'X: ${_getCurrentAccelerometer()?.x.toStringAsFixed(2)}',
'Y: ${_getCurrentAccelerometer()?.y.toStringAsFixed(2)}',
'Z: ${_getCurrentAccelerometer()?.z.toStringAsFixed(2)}',
),
],
),
),
);
}
Widget _buildGyroscopeCard() {
return Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'陀螺仪',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16),
_buildDataChart(
_gyroscopeData.map((data) => data.magnitude).toList(),
'角速度 (rad/s)',
Colors.green,
),
SizedBox(height: 16),
_buildCurrentValues(
'X: ${_getCurrentGyroscope()?.x.toStringAsFixed(2)}',
'Y: ${_getCurrentGyroscope()?.y.toStringAsFixed(2)}',
'Z: ${_getCurrentGyroscope()?.z.toStringAsFixed(2)}',
),
],
),
),
);
}
Widget _buildMagnetometerCard() {
return Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'磁力计',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16),
_buildDataChart(
_magnetometerData.map((data) => data.magnitude).toList(),
'磁场强度 (μT)',
Colors.red,
),
SizedBox(height: 16),
_buildCurrentValues(
'X: ${_getCurrentMagnetometer()?.x.toStringAsFixed(2)}',
'Y: ${_getCurrentMagnetometer()?.y.toStringAsFixed(2)}',
'Z: ${_getCurrentMagnetometer()?.z.toStringAsFixed(2)}',
),
],
),
),
);
}
Widget _buildDataChart(List<double> data, String label, Color color) {
return Container(
height: 150,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label),
SizedBox(height: 8),
Expanded(
child: data.isEmpty
? Center(child: Text('等待数据...'))
: LineChart(
LineChartData(
lineBarsData: [
LineChartBarData(
spots: data.asMap().entries.map((entry) {
return FlSpot(entry.key.toDouble(), entry.value);
}).toList(),
isCurved: true,
color: color,
strokeWidth: 2,
),
],
titlesData: FlTitlesData(show: false),
gridData: FlGridData(show: false),
borderData: FlBorderData(show: false),
minX: 0,
maxX: (data.length - 1).toDouble(),
minY: 0,
maxY: data.isNotEmpty ? data.reduce((a, b) => a > b ? a : b) * 1.2 : 1,
),
),
),
],
),
);
}
Widget _buildCurrentValues(String x, String y, String z) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildValueChip('X', x, Colors.red),
_buildValueChip('Y', y, Colors.green),
_buildValueChip('Z', z, Colors.blue),
],
);
}
Widget _buildValueChip(String label, String value, Color color) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Column(
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: color,
),
),
SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
Widget _build3DVisualization() {
return Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'3D 方向可视化',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16),
Container(
height: 200,
child: Center(
child: CustomPaint(
painter: CompassPainter(
accelerometer: _getCurrentAccelerometer(),
magnetometer: _getCurrentMagnetometer(),
),
child: Container(
width: 200,
height: 200,
),
),
),
),
],
),
),
);
}
AccelerometerData? _getCurrentAccelerometer() {
return _accelerometerData.isNotEmpty ? _accelerometerData.last : null;
}
GyroscopeData? _getCurrentGyroscope() {
return _gyroscopeData.isNotEmpty ? _gyroscopeData.last : null;
}
MagnetometerData? _getCurrentMagnetometer() {
return _magnetometerData.isNotEmpty ? _magnetometerData.last : null;
}
Future<void> _startSensorListening() async {
try {
await _accelerometerManager.startListening();
await _gyroscopeManager.startListening();
await _magnetometerManager.startListening();
_accelerometerManager.accelerometerStream.listen((data) {
setState(() {
_accelerometerData.add(data);
if (_accelerometerData.length > 50) {
_accelerometerData.removeAt(0);
}
});
});
_gyroscopeManager.gyroscopeStream.listen((data) {
setState(() {
_gyroscopeData.add(data);
if (_gyroscopeData.length > 50) {
_gyroscopeData.removeAt(0);
}
});
});
_magnetometerManager.magnetometerStream.listen((data) {
setState(() {
_magnetometerData.add(data);
if (_magnetometerData.length > 50) {
_magnetometerData.removeAt(0);
}
});
});
} catch (e) {
_showErrorDialog('传感器启动失败', e.toString());
}
}
void _stopSensorListening() {
_accelerometerManager.stopListening();
_gyroscopeManager.stopListening();
_magnetometerManager.stopListening();
}
void _showErrorDialog(String title, String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('确定'),
),
],
),
);
}
}
class CompassPainter extends CustomPainter {
final AccelerometerData? accelerometer;
final MagnetometerData? magnetometer;
CompassPainter({this.accelerometer, this.magnetometer});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = math.min(size.width, size.height) / 2 - 20;
// 绘制外圆
final paint = Paint()
..color = Colors.grey[300]!
..style = PaintingStyle.stroke
..strokeWidth = 2;
canvas.drawCircle(center, radius, paint);
// 绘制方向标记
_drawDirectionMarks(canvas, center, radius);
// 绘制指针
if (magnetometer != null) {
_drawCompassNeedle(canvas, center, radius, magnetometer!.heading);
}
}
void _drawDirectionMarks(Canvas canvas, Offset center, double radius) {
final paint = Paint()
..color = Colors.black
..strokeWidth = 2;
// 绘制主要方向
final directions = [
{'text': 'N', 'angle': 0},
{'text': 'E', 'angle': 90},
{'text': 'S', 'angle': 180},
{'text': 'W', 'angle': 270},
];
for (final direction in directions) {
final angle = direction['angle']! * math.pi / 180;
final x = center.dx + radius * 0.8 * math.sin(angle);
final y = center.dy - radius * 0.8 * math.cos(angle);
canvas.drawCircle(Offset(x, y), 4, paint);
final textPainter = TextPainter(
text: TextSpan(
text: direction['text'] as String,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(
x - textPainter.width / 2,
y - textPainter.height / 2,
),
);
}
}
void _drawCompassNeedle(Canvas canvas, Offset center, double radius, double heading) {
final paint = Paint()
..color = Colors.red
..strokeWidth = 4
..strokeCap = StrokeCap.round;
final angle = heading * math.pi / 180;
final needleLength = radius * 0.7;
final endX = center.dx + needleLength * math.sin(angle);
final endY = center.dy - needleLength * math.cos(angle);
canvas.drawLine(center, Offset(endX, endY), paint);
// 绘制中心点
final centerPaint = Paint()
..color = Colors.black
..style = PaintingStyle.fill;
canvas.drawCircle(center, 8, centerPaint);
}
@override
bool shouldRepaint(covariant CompassPainter oldDelegate) {
return accelerometer != oldDelegate.accelerometer ||
magnetometer != oldDelegate.magnetometer;
}
}故事结局:小陈的成功
经过几个月的开发,小陈的健身追踪应用终于完成了!应用能够准确监测用户的运动状态、步数、方向等信息,并提供直观的数据可视化。
"传感器技术为健身应用提供了丰富的数据源,通过合理的传感器融合和数据处理,我们打造出了专业的运动追踪体验。"小陈在项目总结中写道,"特别是功耗优化和数据滤波,确保了应用的实用性和稳定性。"
小陈的应用获得了用户的好评,特别是准确的运动检测和直观的数据展示。他的成功证明了:掌握传感器桥接技术,是开发健康健身类应用的关键技能。
总结
通过小陈的健身追踪应用开发故事,我们全面学习了 Flutter 传感器桥接技术:
核心技术
- 运动传感器:加速度计、陀螺仪、磁力计
- 环境传感器:光线传感器、距离传感器
- 传感器融合:多传感器数据融合算法
- 姿态检测:方向计算和运动识别
高级特性
- 步数检测:基于加速度计的步数算法
- 数据滤波:噪声过滤和数据平滑
- 功耗优化:采样频率控制和传感器管理
- 数据可视化:实时图表和 3D 可视化
最佳实践
- 权限管理:传感器权限的申请和处理
- 性能优化:数据缓存和内存管理
- 错误处理:传感器异常和设备兼容性
- 用户体验:直观的数据展示和反馈
传感器桥接技术为 Flutter 应用打开了物理世界感知的大门,让开发者能够构建出智能的感知应用。掌握这些技术,将帮助你在移动应用开发中创造更多可能!

