Flutter for OpenHarmony 身体健康状况记录App实战 - 体重趋势实现

#

前言

体重管理是很多人关注的健康话题,体重趋势页面需要展示体重的变化曲线,帮助用户了解自己的体重变化规律。通过可视化的图表,用户可以直观地看到减重或增重的效果。

这篇文章会讲解体重趋势页面的实现,包括汇总数据卡片和趋势图表两个核心组件。我们会用到 fl_chart 库来绘制折线图。


页面整体结构

体重趋势页面结构简洁,主要包含汇总数据卡片和趋势图表两个部分。

dart 复制代码
class WeightTrendPage extends StatelessWidget {
  const WeightTrendPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFFFAFAFC),
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        leading: IconButton(
          icon: Icon(Icons.arrow_back_ios_rounded, size: 20.w), 
          onPressed: () => Get.back()
        ),
        title: Text('体重趋势', style: TextStyle(fontSize: 17.sp, fontWeight: FontWeight.w600)),
        centerTitle: true,
      ),
      body: SingleChildScrollView(
        padding: EdgeInsets.all(20.w),
        child: Column(
          children: [
            _buildSummaryCard(),
            SizedBox(height: 20.h),
            _buildChart(),
          ],
        ),
      ),
    );
  }
}

页面使用统一的浅灰色背景,AppBar 透明背景配合居中标题。两个卡片垂直排列,间距为 20.h。


汇总数据卡片

汇总卡片展示当前体重、目标体重和本月变化三个关键数据。

dart 复制代码
Widget _buildSummaryCard() {
  return Container(
    padding: EdgeInsets.all(20.w),
    decoration: BoxDecoration(
      color: Colors.white, 
      borderRadius: BorderRadius.circular(20.r)
    ),
    child: Row(
      children: [
        _buildSummaryItem('当前', '65.5 kg', const Color(0xFF6C63FF)),
        _buildSummaryItem('目标', '63 kg', const Color(0xFF00C9A7)),
        _buildSummaryItem('本月', '-1.2 kg', const Color(0xFFFF6B6B)),
      ],
    ),
  );
}

Widget _buildSummaryItem(String label, String value, Color color) {
  return Expanded(
    child: Column(
      children: [
        Text(label, style: TextStyle(fontSize: 12.sp, color: Colors.grey[500])),
        SizedBox(height: 6.h),
        Text(value, style: TextStyle(
          fontSize: 18.sp, 
          fontWeight: FontWeight.w700, 
          color: color
        )),
      ],
    ),
  );
}

三个数据项用 Expanded 平均分配宽度,每个项目垂直排列标签和数值。当前体重用紫色(应用主题色),目标体重用绿色(表示积极目标),本月变化用红色(表示减重)。

如果本月体重增加,可以把颜色改成其他颜色,或者根据用户的目标(减重/增重)来动态调整颜色。


体重变化方向判断

根据用户的目标和实际变化,可以给出不同的视觉反馈:

dart 复制代码
Color _getChangeColor(double change, String goal) {
  if (goal == '减重') {
    return change < 0 ? const Color(0xFF00C9A7) : const Color(0xFFFF6B6B);
  } else if (goal == '增重') {
    return change > 0 ? const Color(0xFF00C9A7) : const Color(0xFFFF6B6B);
  } else {
    return const Color(0xFF6C63FF);
  }
}

String _formatChange(double change) {
  if (change > 0) {
    return '+${change.toStringAsFixed(1)} kg';
  } else if (change < 0) {
    return '${change.toStringAsFixed(1)} kg';
  } else {
    return '0 kg';
  }
}

减重目标下,体重下降显示绿色,体重上升显示红色;增重目标下则相反。这种设计让用户能直观地了解自己是否朝着目标前进。


趋势图表组件

趋势图表是这个页面的核心,用折线图展示体重的变化趋势。

dart 复制代码
Widget _buildChart() {
  return Container(
    padding: EdgeInsets.all(20.w),
    decoration: BoxDecoration(
      color: Colors.white, 
      borderRadius: BorderRadius.circular(20.r)
    ),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('30天趋势', style: TextStyle(
          fontSize: 16.sp, 
          fontWeight: FontWeight.w600, 
          color: const Color(0xFF1A1A2E)
        )),
        SizedBox(height: 20.h),
        SizedBox(
          height: 200.h,
          child: LineChart(
            LineChartData(
              gridData: FlGridData(show: false),
              titlesData: FlTitlesData(
                rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), 
                topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), 
                leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: false))
              ),
              borderData: FlBorderData(show: false),
              lineBarsData: [
                LineChartBarData(
                  spots: const [
                    FlSpot(0, 66.5), FlSpot(1, 66.2), FlSpot(2, 66.0), 
                    FlSpot(3, 65.8), FlSpot(4, 65.7), FlSpot(5, 65.5), FlSpot(6, 65.5)
                  ],
                  isCurved: true,
                  color: const Color(0xFF6C63FF),
                  barWidth: 3,
                  dotData: FlDotData(show: false),
                  belowBarData: BarAreaData(
                    show: true, 
                    color: const Color(0xFF6C63FF).withOpacity(0.1)
                  ),
                ),
              ],
              minY: 64, maxY: 68,
            ),
          ),
        ),
      ],
    ),
  );
}

图表使用 fl_chart 库的 LineChart 组件。gridData 设置为不显示网格线,让图表看起来更简洁。titlesData 隐藏了左、右、上三边的标签,只保留底部。

折线设置为平滑曲线(isCurved: true),线宽 3 像素,不显示数据点。belowBarData 在折线下方添加了一层淡紫色的填充区域,增强视觉效果。

Y轴范围设置为 64-68,这个范围根据实际数据动态计算会更合理。


动态计算Y轴范围

为了让图表显示效果更好,Y轴范围应该根据实际数据动态计算:

dart 复制代码
Map<String, double> _calculateYRange(List<double> values) {
  if (values.isEmpty) {
    return {'min': 0, 'max': 100};
  }
  
  final min = values.reduce((a, b) => a < b ? a : b);
  final max = values.reduce((a, b) => a > b ? a : b);
  final range = max - min;
  
  // 留出一些边距
  final padding = range * 0.2;
  return {
    'min': (min - padding).floorToDouble(),
    'max': (max + padding).ceilToDouble(),
  };
}

这个方法计算数据的最小值和最大值,然后在两端各留出 20% 的边距,让图表不会太拥挤。


时间范围选择

用户可能想查看不同时间范围的趋势,可以添加一个时间范围选择器:

dart 复制代码
Widget _buildTimeRangeSelector() {
  final ranges = ['7天', '30天', '90天', '1年'];
  final selectedIndex = 1; // 默认选中30天
  
  return Row(
    children: ranges.asMap().entries.map((entry) {
      final isSelected = entry.key == selectedIndex;
      return Expanded(
        child: GestureDetector(
          onTap: () {
            // 切换时间范围
          },
          child: Container(
            margin: EdgeInsets.only(right: entry.key < ranges.length - 1 ? 8.w : 0),
            padding: EdgeInsets.symmetric(vertical: 8.h),
            decoration: BoxDecoration(
              color: isSelected 
                ? const Color(0xFF6C63FF) 
                : const Color(0xFF6C63FF).withOpacity(0.1),
              borderRadius: BorderRadius.circular(8.r),
            ),
            child: Center(
              child: Text(
                entry.value,
                style: TextStyle(
                  fontSize: 12.sp,
                  color: isSelected ? Colors.white : const Color(0xFF6C63FF),
                  fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
                ),
              ),
            ),
          ),
        ),
      );
    }).toList(),
  );
}

四个时间范围选项横向排列,选中的用实心背景,未选中的用淡色背景。这种设计让用户能快速切换查看不同时间段的趋势。


目标线展示

在图表中添加一条目标线,让用户能直观地看到当前体重和目标的差距:

dart 复制代码
LineChartBarData _buildGoalLine(double goalWeight) {
  return LineChartBarData(
    spots: [
      FlSpot(0, goalWeight),
      FlSpot(6, goalWeight),
    ],
    isCurved: false,
    color: const Color(0xFF00C9A7),
    barWidth: 1.5,
    dotData: FlDotData(show: false),
    dashArray: [5, 5], // 虚线
  );
}

目标线用绿色虚线表示,和实际体重曲线形成对比。用户可以清楚地看到自己距离目标还有多远。


数据点详情

当用户点击图表上的某个点时,可以显示该点的详细信息:

dart 复制代码
Widget _buildTooltip(double weight, String date) {
  return Container(
    padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
    decoration: BoxDecoration(
      color: const Color(0xFF1A1A2E),
      borderRadius: BorderRadius.circular(8.r),
    ),
    child: Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text(
          '${weight.toStringAsFixed(1)} kg',
          style: TextStyle(
            fontSize: 14.sp,
            fontWeight: FontWeight.w600,
            color: Colors.white,
          ),
        ),
        Text(
          date,
          style: TextStyle(
            fontSize: 11.sp,
            color: Colors.white70,
          ),
        ),
      ],
    ),
  );
}

提示框用深色背景,显示体重值和日期。这种交互让用户能查看具体某一天的数据。


趋势分析文字

除了图表,还可以添加一段趋势分析的文字说明:

dart 复制代码
Widget _buildTrendAnalysis(double change, int days) {
  String analysis;
  if (change < -1) {
    analysis = '过去$days天体重下降明显,继续保持!';
  } else if (change < 0) {
    analysis = '过去$days天体重稳步下降,效果不错。';
  } else if (change == 0) {
    analysis = '过去$days天体重保持稳定。';
  } else if (change < 1) {
    analysis = '过去$days天体重略有上升,注意饮食。';
  } else {
    analysis = '过去$days天体重上升较多,建议调整计划。';
  }
  
  return Container(
    padding: EdgeInsets.all(12.w),
    decoration: BoxDecoration(
      color: const Color(0xFF6C63FF).withOpacity(0.08),
      borderRadius: BorderRadius.circular(10.r),
    ),
    child: Row(
      children: [
        Icon(Icons.insights_rounded, size: 18.w, color: const Color(0xFF6C63FF)),
        SizedBox(width: 8.w),
        Expanded(
          child: Text(
            analysis,
            style: TextStyle(fontSize: 13.sp, color: const Color(0xFF6C63FF)),
          ),
        ),
      ],
    ),
  );
}

根据体重变化自动生成分析文字,给用户提供个性化的反馈和建议。


小结

体重趋势页面通过汇总数据卡片和趋势图表两个组件,帮助用户全面了解自己的体重变化情况。

核心设计要点包括:三个关键数据用不同颜色区分,折线图下方添加淡色填充增强视觉效果,目标线用虚线表示便于对比。这些设计让用户能直观地追踪自己的体重管理进度。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
雨季6662 小时前
构建 OpenHarmony 文本高亮关键词标记器:用纯字符串操作实现智能标注
开发语言·javascript·flutter·ui·ecmascript·dart
喵手2 小时前
Python爬虫零基础入门【第九章:实战项目教学·第4节】质量报告自动生成:缺失率/重复率/异常值 TopN!
爬虫·python·爬虫实战·python爬虫工程化实战·零基础python爬虫教学·实战项目教学·质量报告自动生成
b2077212 小时前
Flutter for OpenHarmony 身体健康状况记录App实战 - 个人中心实现
android·java·python·flutter·harmonyos
喵手2 小时前
Python爬虫零基础入门【第九章:实战项目教学·第7节】增量采集:last_time / last_id 两种策略各做一遍!
爬虫·python·爬虫实战·python爬虫工程化实战·零基础python爬虫教学·增量采集·策略采集
灰灰勇闯IT2 小时前
Flutter for OpenHarmony:布局组件实战指南
前端·javascript·flutter
子午2 小时前
【2026计算机毕设】水果识别分类系统~python+深度学习+人工智能+算法模型+TensorFlow
人工智能·python·深度学习
No0d1es2 小时前
2023年NOC大赛创客智慧编程赛项Python复赛模拟题(二)
python·青少年编程·noc·复赛·模拟题
BlackWolfSky2 小时前
鸿蒙中级课程笔记3—ArkUI进阶1—属性动画与转场动画
华为·harmonyos
SmartRadio2 小时前
ESP32-S3实现KVM远控+云玩功能 完整方案
运维·python·计算机外设·esp32·kvm·云玩