在这里插入图片描述

前言

体重管理是运动健康应用中帮助用户追踪身体变化的核心功能。通过记录和分析体重数据,用户可以了解运动和饮食对身体的影响,制定科学的减脂或增肌计划。本文将详细介绍如何在Flutter与OpenHarmony平台上实现完善的体重管理组件,包括体重记录、BMI计算、趋势分析、目标设定等功能模块的完整实现方案。

体重管理功能的设计需要考虑数据的准确性和用户的心理感受。体重波动是正常的,我们需要帮助用户理解短期波动和长期趋势的区别,避免因为一两天的体重变化而产生焦虑。同时,提供科学的健康指标和建议,帮助用户建立正确的体重管理观念。

Flutter体重记录模型

class WeightRecord {
  final String id;
  final double weight;
  final DateTime date;
  final String? note;
  final double? bodyFat;
  final double? muscleMass;
  
  WeightRecord({
    required this.id,
    required this.weight,
    required this.date,
    this.note,
    this.bodyFat,
    this.muscleMass,
  });
  
  Map<String, dynamic> toJson() => {
    'id': id,
    'weight': weight,
    'date': date.toIso8601String(),
    'note': note,
    'bodyFat': bodyFat,
    'muscleMass': muscleMass,
  };
  
  factory WeightRecord.fromJson(Map<String, dynamic> json) {
    return WeightRecord(
      id: json['id'],
      weight: json['weight'],
      date: DateTime.parse(json['date']),
      note: json['note'],
      bodyFat: json['bodyFat'],
      muscleMass: json['muscleMass'],
    );
  }
}

体重记录模型定义了体重数据的完整结构。除了核心的体重值和日期外,还支持记录备注、体脂率和肌肉量等扩展数据。备注字段让用户可以记录特殊情况,如饮食变化、生理期等影响体重的因素。体脂率和肌肉量是更专业的身体成分指标,配合智能体脂秤使用。toJson和fromJson方法支持数据的序列化和反序列化,便于存储和传输。这种设计既满足基础需求,又为高级用户提供更详细的数据记录能力。

OpenHarmony体重数据存储

import relationalStore from '@ohos.data.relationalStore';

class WeightStorageService {
  private rdbStore: relationalStore.RdbStore | null = null;
  
  async initDatabase(context: Context): Promise<void> {
    const config: relationalStore.StoreConfig = {
      name: 'weight.db',
      securityLevel: relationalStore.SecurityLevel.S1,
    };
    this.rdbStore = await relationalStore.getRdbStore(context, config);
    
    await this.rdbStore.executeSql(
      'CREATE TABLE IF NOT EXISTS weight_records (id TEXT PRIMARY KEY, weight REAL, date TEXT, note TEXT, body_fat REAL, muscle_mass REAL)'
    );
  }
  
  async addRecord(record: object): Promise<void> {
    if (this.rdbStore) {
      let valueBucket = {
        'id': record['id'],
        'weight': record['weight'],
        'date': record['date'],
        'note': record['note'] || null,
        'body_fat': record['bodyFat'] || null,
        'muscle_mass': record['muscleMass'] || null,
      };
      await this.rdbStore.insert('weight_records', valueBucket);
    }
  }
  
  async getRecordsByDateRange(startDate: string, endDate: string): Promise<Array<object>> {
    let records: Array<object> = [];
    if (this.rdbStore) {
      let predicates = new relationalStore.RdbPredicates('weight_records');
      predicates.between('date', startDate, endDate).orderByAsc('date');
      
      let resultSet = await this.rdbStore.query(predicates);
      while (resultSet.goToNextRow()) {
        records.push({
          id: resultSet.getString(resultSet.getColumnIndex('id')),
          weight: resultSet.getDouble(resultSet.getColumnIndex('weight')),
          date: resultSet.getString(resultSet.getColumnIndex('date')),
        });
      }
      resultSet.close();
    }
    return records;
  }
}

体重数据存储服务使用关系型数据库管理体重记录。表结构包含id、体重、日期、备注、体脂率和肌肉量字段,支持完整的体重数据存储。addRecord方法插入新记录,可选字段使用null处理。getRecordsByDateRange方法按日期范围查询记录,结果按日期升序排列,便于绑制趋势图表。使用关系型数据库的优势是支持复杂查询,如按时间范围筛选、统计平均值等,满足体重分析的各种需求。

Flutter BMI计算组件

class BMICalculator {
  static double calculateBMI(double weightKg, double heightCm) {
    double heightM = heightCm / 100;
    return weightKg / (heightM * heightM);
  }
  
  static String getBMICategory(double bmi) {
    if (bmi < 18.5) return '偏瘦';
    if (bmi < 24) return '正常';
    if (bmi < 28) return '偏胖';
    return '肥胖';
  }
  
  static Color getBMICategoryColor(double bmi) {
    if (bmi < 18.5) return Colors.blue;
    if (bmi < 24) return Colors.green;
    if (bmi < 28) return Colors.orange;
    return Colors.red;
  }
  
  static String getHealthAdvice(double bmi) {
    if (bmi < 18.5) {
      return '您的体重偏轻,建议适当增加营养摄入,配合力量训练增加肌肉量。';
    } else if (bmi < 24) {
      return '您的体重在健康范围内,继续保持良好的运动和饮食习惯。';
    } else if (bmi < 28) {
      return '您的体重略微超标,建议增加有氧运动,控制饮食热量摄入。';
    } else {
      return '您的体重超标较多,建议咨询专业人士制定减重计划。';
    }
  }
}

BMI计算组件提供身体质量指数的计算和解读。calculateBMI方法使用标准公式计算BMI值,体重除以身高的平方。getBMICategory方法根据中国成人BMI标准划分体重类别,18.5以下为偏瘦,18.5-24为正常,24-28为偏胖,28以上为肥胖。getBMICategoryColor方法为不同类别分配颜色,绿色表示健康,红色表示需要关注。getHealthAdvice方法根据BMI提供个性化的健康建议,帮助用户了解自己的身体状况和改进方向。

Flutter体重输入组件

class WeightInputWidget extends StatefulWidget {
  final double? initialWeight;
  final Function(double) onWeightChanged;
  
  const WeightInputWidget({
    Key? key,
    this.initialWeight,
    required this.onWeightChanged,
  }) : super(key: key);
  
  
  State<WeightInputWidget> createState() => _WeightInputWidgetState();
}

class _WeightInputWidgetState extends State<WeightInputWidget> {
  late double _weight;
  
  
  void initState() {
    super.initState();
    _weight = widget.initialWeight ?? 60.0;
  }
  
  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('${_weight.toStringAsFixed(1)} kg', style: TextStyle(fontSize: 48, fontWeight: FontWeight.bold)),
        SizedBox(height: 16),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            IconButton(
              icon: Icon(Icons.remove_circle_outline, size: 36),
              onPressed: () => _adjustWeight(-0.1),
            ),
            SizedBox(width: 16),
            Container(
              width: 200,
              child: Slider(
                value: _weight,
                min: 30,
                max: 150,
                divisions: 1200,
                onChanged: (value) {
                  setState(() => _weight = value);
                  widget.onWeightChanged(value);
                },
              ),
            ),
            SizedBox(width: 16),
            IconButton(
              icon: Icon(Icons.add_circle_outline, size: 36),
              onPressed: () => _adjustWeight(0.1),
            ),
          ],
        ),
      ],
    );
  }
  
  void _adjustWeight(double delta) {
    setState(() {
      _weight = (_weight + delta).clamp(30.0, 150.0);
    });
    widget.onWeightChanged(_weight);
  }
}

体重输入组件提供直观的体重录入界面。中央大字体显示当前体重值,精确到0.1公斤。Slider组件支持滑动快速调整,范围设置为30-150公斤,divisions设为1200实现0.1公斤的精度。两侧的加减按钮支持微调,每次调整0.1公斤,适合精确输入。clamp方法确保体重值不会超出合理范围。这种组合输入方式既支持快速粗调,又支持精确微调,满足不同用户的输入习惯。

OpenHarmony体重同步服务

import http from '@ohos.net.http';

class WeightSyncService {
  async syncToCloud(records: Array<object>): Promise<boolean> {
    let httpRequest = http.createHttp();
    
    try {
      let response = await httpRequest.request(
        'https://api.fitness.com/weight/sync',
        {
          method: http.RequestMethod.POST,
          header: { 'Content-Type': 'application/json' },
          extraData: JSON.stringify({ records: records }),
        }
      );
      return response.responseCode === 200;
    } catch (error) {
      console.error('同步失败: ' + error);
      return false;
    } finally {
      httpRequest.destroy();
    }
  }
  
  async fetchFromCloud(userId: string): Promise<Array<object>> {
    let httpRequest = http.createHttp();
    
    try {
      let response = await httpRequest.request(
        `https://api.fitness.com/weight/records?userId=${userId}`,
        { method: http.RequestMethod.GET }
      );
      
      if (response.responseCode === 200) {
        return JSON.parse(response.result as string);
      }
      return [];
    } catch (error) {
      console.error('获取数据失败: ' + error);
      return [];
    } finally {
      httpRequest.destroy();
    }
  }
}

体重同步服务实现数据的云端备份和多设备同步。syncToCloud方法将本地记录上传到服务器,使用POST请求发送JSON格式的数据。fetchFromCloud方法从服务器获取用户的体重记录,用于新设备登录后恢复数据。两个方法都使用try-catch处理网络异常,finally块确保HTTP请求资源被释放。云同步功能确保用户的体重数据不会因为更换设备而丢失,也支持在多个设备上查看同一份数据。

Flutter体重趋势图表

class WeightTrendChart extends StatelessWidget {
  final List<WeightRecord> records;
  final double? targetWeight;
  
  const WeightTrendChart({
    Key? key,
    required this.records,
    this.targetWeight,
  }) : super(key: key);
  
  
  Widget build(BuildContext context) {
    if (records.isEmpty) {
      return Center(child: Text('暂无数据'));
    }
    
    return Container(
      height: 200,
      padding: EdgeInsets.all(16),
      child: CustomPaint(
        size: Size(double.infinity, 168),
        painter: WeightChartPainter(
          records: records,
          targetWeight: targetWeight,
        ),
      ),
    );
  }
}

class WeightChartPainter extends CustomPainter {
  final List<WeightRecord> records;
  final double? targetWeight;
  
  WeightChartPainter({required this.records, this.targetWeight});
  
  
  void paint(Canvas canvas, Size size) {
    double minWeight = records.map((r) => r.weight).reduce((a, b) => a < b ? a : b) - 2;
    double maxWeight = records.map((r) => r.weight).reduce((a, b) => a > b ? a : b) + 2;
    
    Paint linePaint = Paint()
      ..color = Colors.blue
      ..strokeWidth = 2
      ..style = PaintingStyle.stroke;
    
    Path path = Path();
    double xStep = size.width / (records.length - 1);
    
    for (int i = 0; i < records.length; i++) {
      double x = i * xStep;
      double y = size.height - ((records[i].weight - minWeight) / (maxWeight - minWeight)) * size.height;
      
      if (i == 0) {
        path.moveTo(x, y);
      } else {
        path.lineTo(x, y);
      }
    }
    canvas.drawPath(path, linePaint);
    
    if (targetWeight != null) {
      double targetY = size.height - ((targetWeight! - minWeight) / (maxWeight - minWeight)) * size.height;
      Paint targetPaint = Paint()
        ..color = Colors.green
        ..strokeWidth = 1
        ..style = PaintingStyle.stroke;
      canvas.drawLine(Offset(0, targetY), Offset(size.width, targetY), targetPaint);
    }
  }
  
  
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

体重趋势图表以折线图形式展示体重变化。我们计算数据的最小和最大值,上下各留2公斤的边距,确保曲线不会贴边。蓝色折线连接所有数据点,展示体重的变化趋势。如果设置了目标体重,会绘制一条绿色的水平参考线,让用户直观地看到当前体重与目标的差距。这种图表帮助用户关注长期趋势而非短期波动,建立正确的体重管理心态。

Flutter体重统计卡片

class WeightStatsCard extends StatelessWidget {
  final List<WeightRecord> records;
  final double height;
  
  const WeightStatsCard({
    Key? key,
    required this.records,
    required this.height,
  }) : super(key: key);
  
  
  Widget build(BuildContext context) {
    if (records.isEmpty) return SizedBox.shrink();
    
    double currentWeight = records.last.weight;
    double startWeight = records.first.weight;
    double change = currentWeight - startWeight;
    double bmi = BMICalculator.calculateBMI(currentWeight, height);
    
    return Card(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                _buildStatItem('当前体重', '${currentWeight.toStringAsFixed(1)} kg'),
                _buildStatItem('BMI', bmi.toStringAsFixed(1), color: BMICalculator.getBMICategoryColor(bmi)),
                _buildStatItem('变化', '${change >= 0 ? '+' : ''}${change.toStringAsFixed(1)} kg', 
                    color: change < 0 ? Colors.green : Colors.red),
              ],
            ),
            SizedBox(height: 12),
            Text(BMICalculator.getBMICategory(bmi), style: TextStyle(color: BMICalculator.getBMICategoryColor(bmi), fontWeight: FontWeight.bold)),
          ],
        ),
      ),
    );
  }
  
  Widget _buildStatItem(String label, String value, {Color? color}) {
    return Column(
      children: [
        Text(value, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: color)),
        Text(label, style: TextStyle(color: Colors.grey, fontSize: 12)),
      ],
    );
  }
}

体重统计卡片汇总展示关键的体重指标。当前体重取最新一条记录,变化值计算首条和末条记录的差值,BMI根据当前体重和身高计算。变化值使用颜色区分,减重显示绿色(积极),增重显示红色(需关注)。BMI值和类别使用对应的颜色标识健康状态。这种卡片设计信息密度高,用户一眼就能了解自己的体重状况和变化趋势,是体重管理页面的核心展示组件。

OpenHarmony体重目标服务

import dataPreferences from '@ohos.data.preferences';

class WeightGoalService {
  private preferences: dataPreferences.Preferences | null = null;
  
  async initialize(context: Context): Promise<void> {
    this.preferences = await dataPreferences.getPreferences(context, 'weight_goal');
  }
  
  async setGoal(targetWeight: number, targetDate: string): Promise<void> {
    if (this.preferences) {
      await this.preferences.put('targetWeight', targetWeight);
      await this.preferences.put('targetDate', targetDate);
      await this.preferences.put('startWeight', await this.preferences.get('currentWeight', targetWeight));
      await this.preferences.put('startDate', new Date().toISOString().split('T')[0]);
      await this.preferences.flush();
    }
  }
  
  async getGoal(): Promise<object | null> {
    if (!this.preferences) return null;
    
    let targetWeight = await this.preferences.get('targetWeight', 0) as number;
    if (targetWeight === 0) return null;
    
    return {
      targetWeight: targetWeight,
      targetDate: await this.preferences.get('targetDate', ''),
      startWeight: await this.preferences.get('startWeight', 0),
      startDate: await this.preferences.get('startDate', ''),
    };
  }
  
  async calculateProgress(currentWeight: number): Promise<number> {
    let goal = await this.getGoal();
    if (!goal) return 0;
    
    let totalChange = goal['startWeight'] - goal['targetWeight'];
    let currentChange = goal['startWeight'] - currentWeight;
    
    if (totalChange === 0) return 100;
    return Math.min(100, Math.max(0, (currentChange / totalChange) * 100));
  }
}

体重目标服务管理用户的减重或增重目标。setGoal方法设置目标体重和目标日期,同时记录开始时的体重和日期作为基准。getGoal方法获取当前目标配置,如果未设置目标则返回null。calculateProgress方法计算目标完成进度,通过比较已变化的体重和需要变化的总体重得出百分比。进度值限制在0-100之间,避免超过目标后显示异常。这种目标管理帮助用户设定明确的体重目标,并追踪达成进度。

Flutter体重记录列表

class WeightRecordList extends StatelessWidget {
  final List<WeightRecord> records;
  final Function(String) onDelete;
  
  const WeightRecordList({
    Key? key,
    required this.records,
    required this.onDelete,
  }) : super(key: key);
  
  
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: records.length,
      itemBuilder: (context, index) {
        var record = records[records.length - 1 - index];
        double? change;
        if (index < records.length - 1) {
          var previousRecord = records[records.length - 2 - index];
          change = record.weight - previousRecord.weight;
        }
        
        return Dismissible(
          key: Key(record.id),
          direction: DismissDirection.endToStart,
          onDismissed: (_) => onDelete(record.id),
          background: Container(
            color: Colors.red,
            alignment: Alignment.centerRight,
            padding: EdgeInsets.only(right: 16),
            child: Icon(Icons.delete, color: Colors.white),
          ),
          child: ListTile(
            leading: CircleAvatar(
              backgroundColor: Colors.blue.withOpacity(0.1),
              child: Text('${record.weight.toStringAsFixed(0)}', style: TextStyle(color: Colors.blue)),
            ),
            title: Text('${record.weight.toStringAsFixed(1)} kg'),
            subtitle: Text(_formatDate(record.date)),
            trailing: change != null ? Text(
              '${change >= 0 ? '+' : ''}${change.toStringAsFixed(1)}',
              style: TextStyle(color: change < 0 ? Colors.green : Colors.red),
            ) : null,
          ),
        );
      },
    );
  }
  
  String _formatDate(DateTime date) {
    return '${date.month}${date.day}日';
  }
}

体重记录列表展示用户的历史体重数据。列表按时间倒序排列,最新记录在最上方。每条记录显示体重值、日期和与上一条记录的变化值。变化值使用颜色区分,减重为绿色,增重为红色。Dismissible组件支持左滑删除错误记录。CircleAvatar显示体重的整数部分,提供快速的视觉参考。这种列表设计让用户可以回顾自己的体重变化历程,发现规律和问题。

总结

本文全面介绍了Flutter与OpenHarmony平台上体重管理组件的实现方案。从数据模型到存储服务,从BMI计算到趋势图表,从目标设定到记录管理,涵盖了体重管理功能的各个方面。通过科学的指标计算和直观的数据展示,我们可以帮助用户建立正确的体重管理观念,追踪身体变化,实现健康的体重目标。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

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

更多推荐