Flutter & OpenHarmony 运动App配速计算组件开发

前言
配速是跑步和骑行运动中最重要的指标之一,它反映了运动者每公里或每英里所需的时间。准确的配速计算和展示能够帮助用户控制运动强度,制定训练计划,提升运动表现。本文将详细介绍如何在Flutter与OpenHarmony平台上实现专业的配速计算组件,包括实时配速、平均配速、分段配速、配速区间等功能模块的完整实现方案。
配速计算看似简单,实际上涉及多个技术细节:GPS数据的平滑处理、瞬时速度的稳定计算、分段配速的准确统计等。我们需要在实时性和准确性之间取得平衡,为用户提供既及时又可靠的配速数据。
Flutter配速数据模型
class PaceData {
final Duration pacePerKm;
final double speedKmh;
final DateTime timestamp;
PaceData({
required this.pacePerKm,
required this.speedKmh,
required this.timestamp,
});
String get formattedPace {
int minutes = pacePerKm.inMinutes;
int seconds = pacePerKm.inSeconds % 60;
return "$minutes'${seconds.toString().padLeft(2, '0')}\"";
}
static PaceData fromSpeed(double speedKmh) {
if (speedKmh <= 0) {
return PaceData(
pacePerKm: Duration(minutes: 99, seconds: 59),
speedKmh: 0,
timestamp: DateTime.now(),
);
}
int totalSeconds = (3600 / speedKmh).round();
return PaceData(
pacePerKm: Duration(seconds: totalSeconds),
speedKmh: speedKmh,
timestamp: DateTime.now(),
);
}
}
配速数据模型封装了配速相关的核心数据。pacePerKm表示每公里用时,speedKmh表示时速(公里/小时),两者可以相互转换。formattedPace属性将配速格式化为跑步常用的分秒格式,如"5’30""表示每公里5分30秒。fromSpeed工厂方法从速度计算配速,处理了速度为零的边界情况,避免除零错误。这种模型设计支持配速和速度两种表示方式,满足不同用户的习惯。
OpenHarmony GPS速度服务
import geoLocationManager from '@ohos.geoLocationManager';
class GPSSpeedService {
private speedHistory: Array<number> = [];
private readonly historySize: number = 5;
startSpeedTracking(callback: (speed: number) => void): void {
let request: geoLocationManager.LocationRequest = {
priority: geoLocationManager.LocationRequestPriority.ACCURACY,
scenario: geoLocationManager.LocationRequestScenario.NAVIGATION,
timeInterval: 1,
distanceInterval: 0,
maxAccuracy: 10,
};
geoLocationManager.on('locationChange', request, (location) => {
let speed = location.speed || 0;
let smoothedSpeed = this.smoothSpeed(speed * 3.6);
callback(smoothedSpeed);
});
}
private smoothSpeed(speed: number): number {
this.speedHistory.push(speed);
if (this.speedHistory.length > this.historySize) {
this.speedHistory.shift();
}
let sum = this.speedHistory.reduce((a, b) => a + b, 0);
return sum / this.speedHistory.length;
}
stopSpeedTracking(): void {
geoLocationManager.off('locationChange');
}
}
GPS速度服务从定位系统获取实时速度数据。OpenHarmony的geoLocationManager提供了位置和速度信息,speed字段返回米/秒单位的速度,我们乘以3.6转换为公里/小时。为了减少GPS信号波动导致的速度跳变,我们实现了滑动平均平滑算法,保留最近5个速度值计算平均值。这种平滑处理让配速显示更加稳定,避免数字频繁跳动影响用户体验。timeInterval设为1秒确保及时更新。
Flutter实时配速显示
class RealtimePaceDisplay extends StatelessWidget {
final PaceData currentPace;
final PaceData? targetPace;
const RealtimePaceDisplay({
Key? key,
required this.currentPace,
this.targetPace,
}) : super(key: key);
Widget build(BuildContext context) {
bool isOnTarget = targetPace == null ||
currentPace.pacePerKm <= targetPace!.pacePerKm;
return Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
color: isOnTarget ? Colors.green.withOpacity(0.1) : Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isOnTarget ? Colors.green : Colors.red,
width: 2,
),
),
child: Column(
children: [
Text('当前配速', style: TextStyle(color: Colors.grey)),
SizedBox(height: 8),
Text(
currentPace.formattedPace,
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
color: isOnTarget ? Colors.green : Colors.red,
),
),
if (targetPace != null) ...[
SizedBox(height: 8),
Text(
'目标: ${targetPace!.formattedPace}',
style: TextStyle(color: Colors.grey),
),
],
],
),
);
}
}
实时配速显示组件以醒目的方式展示当前配速。如果设置了目标配速,组件会根据当前配速是否达标显示不同的颜色:达标显示绿色,未达标显示红色。这种即时的视觉反馈帮助用户在运动中快速判断自己的配速状态,及时调整运动强度。大字体的配速数字确保用户在运动中也能轻松看清,边框和背景色的组合增强了视觉区分度。
Flutter配速计算器
class PaceCalculator {
static PaceData calculateAveragePace(double totalDistanceKm, Duration totalDuration) {
if (totalDistanceKm <= 0) {
return PaceData.fromSpeed(0);
}
double speedKmh = totalDistanceKm / (totalDuration.inSeconds / 3600);
return PaceData.fromSpeed(speedKmh);
}
static List<PaceData> calculateSplitPaces(List<Duration> splitTimes, double splitDistanceKm) {
return splitTimes.map((duration) {
double speedKmh = splitDistanceKm / (duration.inSeconds / 3600);
return PaceData.fromSpeed(speedKmh);
}).toList();
}
static Duration estimateFinishTime(double targetDistanceKm, PaceData currentPace) {
if (currentPace.speedKmh <= 0) {
return Duration.zero;
}
double hours = targetDistanceKm / currentPace.speedKmh;
return Duration(seconds: (hours * 3600).round());
}
static PaceData calculateRequiredPace(double remainingDistanceKm, Duration remainingTime) {
if (remainingDistanceKm <= 0 || remainingTime.inSeconds <= 0) {
return PaceData.fromSpeed(0);
}
double requiredSpeedKmh = remainingDistanceKm / (remainingTime.inSeconds / 3600);
return PaceData.fromSpeed(requiredSpeedKmh);
}
}
配速计算器提供多种配速相关的计算功能。calculateAveragePace方法根据总距离和总时间计算平均配速,这是运动结束后最常用的指标。calculateSplitPaces方法计算每公里的分段配速,帮助用户分析配速变化。estimateFinishTime方法根据当前配速预估完成目标距离所需的时间。calculateRequiredPace方法计算要在剩余时间内完成剩余距离所需的配速,用于目标时间控制。这些计算功能覆盖了配速分析的各种场景。
OpenHarmony分段计时服务
class SplitTimerService {
private splitTimes: Array<number> = [];
private lastSplitDistance: number = 0;
private lastSplitTime: number = 0;
private splitDistance: number = 1000;
setSplitDistance(meters: number): void {
this.splitDistance = meters;
}
checkSplit(currentDistance: number, currentTime: number): object | null {
let distanceSinceLastSplit = currentDistance - this.lastSplitDistance;
if (distanceSinceLastSplit >= this.splitDistance) {
let splitDuration = currentTime - this.lastSplitTime;
this.splitTimes.push(splitDuration);
let splitNumber = this.splitTimes.length;
this.lastSplitDistance = currentDistance;
this.lastSplitTime = currentTime;
return {
splitNumber: splitNumber,
splitTime: splitDuration,
totalSplits: this.splitTimes.length,
};
}
return null;
}
getSplitTimes(): Array<number> {
return [...this.splitTimes];
}
reset(): void {
this.splitTimes = [];
this.lastSplitDistance = 0;
this.lastSplitTime = 0;
}
}
分段计时服务自动记录每公里(或自定义距离)的用时。checkSplit方法在每次位置更新时调用,检查是否完成了一个分段。当累计距离达到分段距离时,记录该分段的用时并返回分段信息。splitDistance默认为1000米(1公里),用户可以通过setSplitDistance方法自定义。getSplitTimes方法返回所有分段时间的副本,用于分析和展示。reset方法在新运动开始时清空历史数据。这种服务设计让分段统计自动化,无需用户手动操作。
Flutter分段配速列表
class SplitPaceList extends StatelessWidget {
final List<Duration> splitTimes;
final double splitDistanceKm;
const SplitPaceList({
Key? key,
required this.splitTimes,
this.splitDistanceKm = 1.0,
}) : super(key: key);
Widget build(BuildContext context) {
List<PaceData> paces = PaceCalculator.calculateSplitPaces(splitTimes, splitDistanceKm);
Duration? fastestSplit = splitTimes.isNotEmpty
? splitTimes.reduce((a, b) => a < b ? a : b)
: null;
return ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: splitTimes.length,
itemBuilder: (context, index) {
bool isFastest = splitTimes[index] == fastestSplit;
return ListTile(
leading: CircleAvatar(
backgroundColor: isFastest ? Colors.amber : Colors.blue,
child: Text('${index + 1}', style: TextStyle(color: Colors.white)),
),
title: Text(paces[index].formattedPace),
subtitle: Text('用时: ${_formatDuration(splitTimes[index])}'),
trailing: isFastest ? Icon(Icons.star, color: Colors.amber) : null,
);
},
);
}
String _formatDuration(Duration d) {
int minutes = d.inMinutes;
int seconds = d.inSeconds % 60;
return '$minutes:${seconds.toString().padLeft(2, '0')}';
}
}
分段配速列表展示每公里的配速和用时。每个列表项显示公里数编号、配速和用时,最快的分段使用金色背景和星标突出显示,给用户成就感。CircleAvatar显示公里数,视觉上清晰区分每一项。通过比较所有分段时间找出最快分段,这个信息帮助用户了解自己的最佳表现。列表使用shrinkWrap和NeverScrollableScrollPhysics适配嵌入到其他可滚动容器中的场景。
Flutter配速区间组件
class PaceZoneIndicator extends StatelessWidget {
final PaceData currentPace;
final int userAge;
const PaceZoneIndicator({
Key? key,
required this.currentPace,
required this.userAge,
}) : super(key: key);
String _getPaceZone() {
int paceSeconds = currentPace.pacePerKm.inSeconds;
if (paceSeconds > 480) return '恢复跑';
if (paceSeconds > 390) return '轻松跑';
if (paceSeconds > 330) return '马拉松配速';
if (paceSeconds > 300) return '乳酸阈值跑';
if (paceSeconds > 240) return '间歇跑';
return '冲刺跑';
}
Color _getZoneColor() {
int paceSeconds = currentPace.pacePerKm.inSeconds;
if (paceSeconds > 480) return Colors.grey;
if (paceSeconds > 390) return Colors.green;
if (paceSeconds > 330) return Colors.lightGreen;
if (paceSeconds > 300) return Colors.orange;
if (paceSeconds > 240) return Colors.deepOrange;
return Colors.red;
}
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: _getZoneColor(),
borderRadius: BorderRadius.circular(20),
),
child: Text(
_getPaceZone(),
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
);
}
}
配速区间组件根据当前配速显示对应的训练区间。我们定义了六个配速区间:恢复跑、轻松跑、马拉松配速、乳酸阈值跑、间歇跑和冲刺跑,每个区间对应不同的训练目的和强度。区间划分基于配速秒数,不同区间使用不同颜色标识,从灰色(最慢)到红色(最快)形成直观的强度梯度。这种区间显示帮助用户理解当前配速的训练意义,指导他们根据训练目标调整配速。
OpenHarmony配速数据存储
import dataPreferences from '@ohos.data.preferences';
class PaceDataStorage {
private preferences: dataPreferences.Preferences | null = null;
async initialize(context: Context): Promise<void> {
this.preferences = await dataPreferences.getPreferences(context, 'pace_data');
}
async saveBestPace(distance: string, paceSeconds: number): Promise<void> {
if (this.preferences) {
let key = `best_pace_${distance}`;
let currentBest = await this.preferences.get(key, 999999) as number;
if (paceSeconds < currentBest) {
await this.preferences.put(key, paceSeconds);
await this.preferences.flush();
}
}
}
async getBestPace(distance: string): Promise<number | null> {
if (this.preferences) {
let key = `best_pace_${distance}`;
let value = await this.preferences.get(key, -1) as number;
return value > 0 ? value : null;
}
return null;
}
async saveRecentPaces(paces: Array<number>): Promise<void> {
if (this.preferences) {
await this.preferences.put('recent_paces', JSON.stringify(paces.slice(-10)));
await this.preferences.flush();
}
}
}
配速数据存储服务管理用户的配速记录。saveBestPace方法保存特定距离的最佳配速,只有当新配速优于历史最佳时才更新,实现个人记录的自动追踪。getBestPace方法获取指定距离的最佳配速,用于显示个人记录和比较。saveRecentPaces方法保存最近的配速数据,限制为最近10条,用于分析配速趋势。这种存储设计支持多种距离的最佳配速记录,如1公里、5公里、10公里等。
Flutter配速目标设置
class PaceTargetSetting extends StatefulWidget {
final PaceData? currentTarget;
final Function(PaceData) onTargetSet;
const PaceTargetSetting({
Key? key,
this.currentTarget,
required this.onTargetSet,
}) : super(key: key);
State<PaceTargetSetting> createState() => _PaceTargetSettingState();
}
class _PaceTargetSettingState extends State<PaceTargetSetting> {
int _minutes = 5;
int _seconds = 30;
void initState() {
super.initState();
if (widget.currentTarget != null) {
_minutes = widget.currentTarget!.pacePerKm.inMinutes;
_seconds = widget.currentTarget!.pacePerKm.inSeconds % 60;
}
}
Widget build(BuildContext context) {
return Column(
children: [
Text('目标配速', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildPicker('分', _minutes, 3, 15, (v) => setState(() => _minutes = v)),
Text("'", style: TextStyle(fontSize: 32)),
_buildPicker('秒', _seconds, 0, 59, (v) => setState(() => _seconds = v)),
Text('"', style: TextStyle(fontSize: 32)),
],
),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
Duration pace = Duration(minutes: _minutes, seconds: _seconds);
double speedKmh = 3600 / pace.inSeconds;
widget.onTargetSet(PaceData(
pacePerKm: pace,
speedKmh: speedKmh,
timestamp: DateTime.now(),
));
},
child: Text('设置目标'),
),
],
);
}
Widget _buildPicker(String label, int value, int min, int max, Function(int) onChanged) {
return Column(
children: [
Text(label, style: TextStyle(color: Colors.grey, fontSize: 12)),
SizedBox(
width: 60,
height: 100,
child: ListWheelScrollView.useDelegate(
itemExtent: 40,
onSelectedItemChanged: onChanged,
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) {
int displayValue = min + index;
return Center(
child: Text(
displayValue.toString().padLeft(2, '0'),
style: TextStyle(fontSize: 24, fontWeight: displayValue == value ? FontWeight.bold : FontWeight.normal),
),
);
},
childCount: max - min + 1,
),
),
),
],
);
}
}
配速目标设置组件让用户设定目标配速。我们使用ListWheelScrollView创建滚轮选择器,分别选择分钟和秒数,这种交互方式在移动端非常直观。选择器范围设置为3-15分钟和0-59秒,覆盖了从快速跑到慢跑的所有配速。设置完成后,组件将配速转换为PaceData对象通过回调返回。目标配速在运动过程中用于实时比较,帮助用户控制运动强度。
总结
本文全面介绍了Flutter与OpenHarmony平台上配速计算组件的实现方案。从数据模型到GPS速度获取,从实时显示到分段统计,从配速区间到目标设置,涵盖了配速功能的各个方面。通过准确的计算和直观的展示,我们可以帮助用户更好地了解和控制自己的运动配速,提升训练效果和运动体验。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)