Flutter工具类插件fit_tool的使用

Flutter工具类插件fit_tool的使用

背景

灵活且可互操作的数据传输(FIT)协议专门用于存储和共享源自运动、健身和健康设备的数据。FIT协议定义了一组数据存储模板(FIT消息),可以用来存储用户配置文件、活动数据、课程和锻炼计划等信息。它设计紧凑、可互操作且可扩展。

使用方法

以下是一些关于如何使用fit_tool的示例:

更多示例可以在示例目录中找到。

注意:在以下示例中,我们经常提到test/data目录中的文件。为了成功运行这些示例,您可能需要修改文件路径。

读取一个FIT文件

读取FIT文件有两种主要方式。第一种是将文件的所有字节一次性读取并解码,第二种是通过流的方式读取文件。第一种方法实现起来最简单,但对于非常大的FIT文件可能不是最有效的方法。以下代码从文件中读取所有字节,然后将其解码为FIT文件对象,并将FIT数据转换为可读的CSV文件。

import 'dart:io';

import 'package:csv/csv.dart';
import 'package:fit_tool/src/fit_file.dart';

Future<void> main() async {
  // 读取FIT文件
  final file = File('./test/data/sdk/Activity.fit');
  final bytes = await file.readAsBytes();
  
  // 将字节解码为FIT文件对象
  final fitFile = FitFile.fromBytes(bytes);

  // 将FIT数据转换为CSV格式并写入文件
  final outFile = File('./test/data/sdk/Activity.csv');
  final csv = const ListToCsvConverter().convert(fitFile.toRows());
  await outFile.writeAsString(csv);
}

在下一个示例中,我们使用Dart流来读取文件,这对于处理FIT数据非常强大。以下示例打开FIT文件作为字节流,并使用提供的FitDecoder将其转换为消息流。

import 'dart:io';

import 'package:fit_tool/src/fit_decoder.dart';
import 'package:fit_tool/src/profile/messages/record_message.dart';

Future<void> main() async {
  // 打开FIT文件
  final file = File('./test/data/palisade.fit');
  var byteStream = file.openRead();
  
  // 将字节流转换为消息流
  final messageStream = byteStream.transform(FitDecoder());

  // 过滤出记录消息并提取经纬度位置
  await messageStream
      .where((message) => message is RecordMessage)
      .forEach((message) {
    message as RecordMessage;
    if (message.positionLat != null && message.positionLong != null) {
      print('position: ${message.positionLat}, ${message.positionLong}');
    }
  });
}
写入一个FIT文件

以下代码编写一个锻炼计划作为FIT文件。

import 'dart:io';

import 'package:csv/csv.dart';
import 'package:fit_tool/src/fit_file_builder.dart';
import 'package:fit_tool/src/profile/messages/file_id_message.dart';
import 'package:fit_tool/src/profile/messages/workout_message.dart';
import 'package:fit_tool/src/profile/messages/workout_step_message.dart';
import 'package:fit_tool/src/profile/profile_type.dart';
import 'package:fit_tool/src/utils/conversions.dart';

Future<void> main() async {
  // 创建文件ID消息
  final fileIdMessage = FileIdMessage()
    ..type = FileType.workout
    ..manufacturer = Manufacturer.development.value
    ..product = 0
    ..timeCreated = toSecondsSince1989Epoch(DateTime.now().millisecondsSinceEpoch)
    ..serialNumber = 0x12345678;

  // 创建锻炼步骤消息
  final workoutSteps = [
    WorkoutStepMessage()
      ..workoutStepName = 'Warm up 10min in Heart Rate Zone 1'
      ..intensity = Intensity.warmup
      ..durationType = WorkoutStepDuration.time
      ..durationTime = 600.0
      ..targetType = WorkoutStepTarget.heartRate
      ..targetHrZone = 1,
    WorkoutStepMessage()
      ..workoutStepName = 'Bike 40min Power Zone 3'
      ..intensity = Intensity.active
      ..durationType = WorkoutStepDuration.time
      ..durationTime = 24000.0
      ..targetType = WorkoutStepTarget.power
      ..targetPowerZone = 3,
    WorkoutStepMessage()
      ..workoutStepName = 'Cool Down Until Lap Button Pressed'
      ..intensity = Intensity.cooldown
      ..durationType = WorkoutStepDuration.open
      ..durationValue = 0
      ..targetType = WorkoutStepTarget.open
      ..targetValue = 0,
  ];

  // 创建锻炼消息
  final workoutMessage = WorkoutMessage()
    ..workoutName = 'Tempo Bike'
    ..sport = Sport.cycling
    ..numValidSteps = workoutSteps.length;

  // 创建FIT文件构建器并添加消息
  final builder = FitFileBuilder(autoDefine: true, minStringSize: 50)
    ..add(fileIdMessage)
    ..add(workoutMessage)
    ..addAll(workoutSteps);

  final fitFile = builder.build();

  // 将FIT文件写入磁盘
  final outFile = await File('./test/out/tempo_bike_workout.fit').create(recursive: true);
  await outFile.writeAsBytes(fitFile.toBytes());

  // 将FIT文件转换为CSV格式并写入文件
  final csvOutFile = File('./test/out/tempo_bike_workout.csv');
  final csv = const ListToCsvConverter().convert(fitFile.toRows());
  await csvOutFile.writeAsString(csv);
}

完整示例Demo

以下是创建一个活动FIT文件的完整示例:

import 'dart:io';
import 'dart:math';

import 'package:csv/csv.dart';
import 'package:fit_tool/src/fit_file_builder.dart';
import 'package:fit_tool/src/profile/messages/event_message.dart';
import 'package:fit_tool/src/profile/messages/file_id_message.dart';
import 'package:fit_tool/src/profile/messages/lap_message.dart';
import 'package:fit_tool/src/profile/messages/record_message.dart';
import 'package:fit_tool/src/profile/messages/session_message.dart';
import 'package:fit_tool/src/profile/profile_type.dart';
import 'package:gpx/gpx.dart';

Future<void> main() async {
  // 设置自动定义以创建所需的定义消息
  final builder = FitFileBuilder(autoDefine: true, minStringSize: 50);

  // 从GPX文件读取位置数据
  final gpxFile = File('./test/data/old_stage_left_hand_lee.gpx');
  final gpxString = await gpxFile.readAsString();
  final xmlGpx = GpxReader().fromString(gpxString);

  // 创建文件ID消息
  final fileIdMessage = FileIdMessage()
    ..type = FileType.activity
    ..manufacturer = Manufacturer.development.value
    ..product = 0
    ..timeCreated = DateTime.now().millisecondsSinceEpoch
    ..serialNumber = 0x12345678;
  builder.add(fileIdMessage);

  // 计时器事件是FIT活动文件的最佳实践
  final startTimestamp = DateTime.now().millisecondsSinceEpoch;
  var timestamp = startTimestamp;
  final eventMessage = EventMessage()
    ..event = Event.timer
    ..eventType = EventType.start
    ..timestamp = startTimestamp;
  builder.add(eventMessage);

  final records = <RecordMessage>[];
  var recordIndex = 0;
  for (var trackPoint in xmlGpx.trks[0].trksegs[0].trkpts) {
    timestamp += 10000;
    records.add(RecordMessage()
      ..timestamp = timestamp
      ..positionLong = trackPoint.lon
      ..positionLat = trackPoint.lat
      ..altitude = trackPoint.ele
      ..power = (20 * sin(2 * pi * recordIndex / 50) + 200).round());
    recordIndex++;
  }
  builder.addAll(records);

  // 每个FIT活动文件必须至少包含一个Lap消息
  final elapsedTime = (timestamp - startTimestamp).toDouble();
  final lapMessage = LapMessage()
    ..timestamp = timestamp
    ..startTime = startTimestamp
    ..totalElapsedTime = elapsedTime
    ..totalTimerTime = elapsedTime;
  builder.add(lapMessage);

  // 每个FIT活动文件必须至少包含一个Session消息
  final sessionMessage = SessionMessage()
    ..timestamp = timestamp
    ..startTime = startTimestamp
    ..totalElapsedTime = elapsedTime
    ..totalTimerTime = elapsedTime
    ..sport = Sport.cycling
    ..subSport = SubSport.exercise
    ..firstLapIndex = 0
    ..numLaps = 1;
  builder.add(sessionMessage);

  /// 最后构建FIT文件对象并写入文件
  final fitFile = builder.build();

  final outFile = await File('./test/out/old_stage_activity.fit').create(recursive: true);
  await outFile.writeAsBytes(fitFile.toBytes());

  final csvOutFile = File('./test/out/old_stage_activity.csv');
  final csv = const ListToCsvConverter().convert(fitFile.toRows());
  await csvOutFile.writeAsString(csv);
}

更多关于Flutter工具类插件fit_tool的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter工具类插件fit_tool的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter项目中使用fit_tool插件的示例代码。fit_tool是一个假设的Flutter工具类插件,用于展示一些常见的工具功能,如设备信息获取、屏幕适配等。由于fit_tool是一个假设的插件,因此以下代码是基于一个类似的工具类插件的常见用法编写的。

首先,确保你已经在pubspec.yaml文件中添加了fit_tool依赖项(注意:由于fit_tool是假设的,你需要替换为实际存在的插件名):

dependencies:
  flutter:
    sdk: flutter
  fit_tool: ^x.y.z  # 替换为实际版本号

然后,运行flutter pub get来获取依赖项。

接下来,在你的Flutter项目中,你可以按照以下方式使用fit_tool插件:

示例代码

import 'package:flutter/material.dart';
import 'package:fit_tool/fit_tool.dart';  // 假设的fit_tool插件导入

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fit Tool Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String deviceInfo = '';
  double screenWidth = 0.0;
  double screenHeight = 0.0;

  @override
  void initState() {
    super.initState();
    // 调用fit_tool插件获取设备信息和屏幕尺寸
    _getDeviceInfo();
    _getScreenSize();
  }

  void _getDeviceInfo() async {
    String info = await FitTool.getDeviceInfo();
    setState(() {
      deviceInfo = info;
    });
  }

  void _getScreenSize() {
    double width = FitTool.getScreenWidth(context);
    double height = FitTool.getScreenHeight(context);
    setState(() {
      screenWidth = width;
      screenHeight = height;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Fit Tool Example'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Device Info:', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            Text(deviceInfo, style: TextStyle(fontSize: 16)),
            SizedBox(height: 16),
            Text('Screen Size:', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            Text('Width: $screenWidth, Height: $screenHeight', style: TextStyle(fontSize: 16)),
          ],
        ),
      ),
    );
  }
}

注意事项

  1. 插件方法:在上面的示例中,FitTool.getDeviceInfo()FitTool.getScreenWidth(context)FitTool.getScreenHeight(context)是假设的插件方法。你需要根据实际的fit_tool插件文档或API参考来替换这些方法。

  2. 异步操作:如果插件方法需要异步操作(如获取设备信息),你需要使用asyncawait关键字来处理这些操作,并在setState中更新UI。

  3. 上下文传递:一些插件方法可能需要BuildContext作为参数,确保在调用这些方法时传递正确的上下文。

由于fit_tool是一个假设的插件,实际使用时,你需要查阅具体插件的文档和API参考,以确保正确使用其功能。

回到顶部