在这里插入图片描述

前言

配速是跑步和骑行运动中最重要的指标之一,它反映了运动者每公里或每英里所需的时间。准确的配速计算和展示能够帮助用户控制运动强度,制定训练计划,提升运动表现。本文将详细介绍如何在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

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐