首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Flutter for OpenHarmony BMI 健康计算器:打造支持深色模式的智能健康工具

Flutter for OpenHarmony BMI 健康计算器:打造支持深色模式的智能健康工具

作者头像
晚霞的不甘
发布2026-02-09 17:32:51
发布2026-02-09 17:32:51
960
举报

Flutter for OpenHarmony BMI 健康计算器:打造支持深色模式的智能健康工具

在健康管理日益普及的今天,身体质量指数(BMI) 作为衡量体重是否健康的简易指标,已成为大众日常关注的焦点。而一个优秀的 BMI 计算器,不仅要准确计算数值,更应提供清晰的健康解读、友好的交互体验与个性化的视觉适配

🌐 加入社区 欢迎加入 开源鸿蒙跨平台开发者社区,获取最新资源与技术支持: 👉 开源鸿蒙跨平台开发者社区


完整效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、核心功能:从输入到健康建议

该应用的核心逻辑简洁而完整:

  1. 用户输入:身高(cm)、体重(kg);
  2. 数据校验:非空、正数、有效数字;
  3. BMI 计算:公式为 体重(kg) / (身高(m))²
  4. 健康分级
    • < 18.5:偏瘦(橙色提示)
    • 18.5–23.9:正常(绿色鼓励)
    • 24–27.9:超重(橙红警示)
    • ≥ 28:肥胖(红色警告)
  5. 结果展示:大号数值 + 个性化健康建议。

💡 不仅告诉你“是多少”,更告诉你“意味着什么”。


二、智能输入处理与错误反馈

1. 安全解析用户输入
代码语言:javascript
复制
final height = double.tryParse(heightStr);
final weight = double.tryParse(weightStr);

if (height == null || weight == null || height <= 0 || weight <= 0) {
  _showError('请输入有效的正数');
  return;
}
在这里插入图片描述
在这里插入图片描述

  • 使用 double.tryParse() 避免格式错误导致崩溃;
  • 显式检查非正数,防止无效计算(如除零或负值)。
2. 即时错误提示

  • 通过 ScaffoldMessenger.of(context).showSnackBar() 显示轻量级提示;
  • 错误信息具体(“请输入身高和体重” vs “输入无效”),提升可用性。

三、双主题系统:自动适配用户偏好

1. 主题定义
代码语言:javascript
复制
theme: ThemeData( /* 亮色主题 */ ),
darkTheme: ThemeData( /* 深色主题 */ ),
themeMode: ThemeMode.system, // 跟随系统
  • 亮色模式:背景 grey[50],输入框白色;
  • 深色模式:背景 #121212,输入框 #1E1E1E
  • 自动切换:尊重用户系统设置,无需手动选择。
2. 动态颜色获取
代码语言:javascript
复制
final isDark = Theme.of(context).brightness == Brightness.dark;
final textColor = isDark ? Colors.white : Colors.black87;
final cardColor = isDark ? const Color(0xFF1E1E1E) : Colors.white;
在这里插入图片描述
在这里插入图片描述
  • build 方法中实时判断当前主题;
  • 确保文字、卡片等元素在两种模式下均有良好可读性。

无障碍设计:高对比度文本 + 清晰图标,照顾不同视觉需求。


四、UI/UX 设计亮点

1. 输入体验优化
  • 圆角无边框输入框OutlineInputBorder(borderSide: BorderSide.none)
  • 前缀图标Icons.heightIcons.monitor_weight 直观提示字段含义;
  • 占位符示例:“例如:175”,降低用户认知负担。
2. 按钮层级分明

按钮

类型

视觉权重

作用

计算 BMI

ElevatedButton

高(主色填充)

核心操作

重置

TextButton

低(文字链接)

辅助操作

3. 结果区域动态呈现
  • 未计算时:显示引导插画(Icons.monitor_heart)+ 文案;
  • 计算后:以 Card 形式展示结果,包含:
    • 标题“你的 BMI”
    • 超大字号数值(48pt),突出关键信息;
    • 彩色编码:绿色=健康,红色=风险,一目了然;
    • 个性化建议:语气积极(“继续保持!”)或关切(“建议咨询医生”)。
4. 平滑动画过渡
代码语言:javascript
复制
AnimatedContainer(
  duration: const Duration(milliseconds: 500),
  curve: Curves.easeOut,
  child: Card(...),
)
  • 结果卡片出现时带有淡入+缩放感(由 AnimatedContainer 驱动);
  • 提升交互流畅度,避免生硬切换。

五、技术实现细节

技术点

应用说明

TextEditingController

精确控制输入框内容,支持清空(clear())

SingleChildScrollView

确保小屏幕设备可滚动查看全部内容

CrossAxisAlignment.stretch

使按钮与输入框宽度一致,布局整齐

toStringAsFixed(1)

保留一位小数,避免冗长数字(如 22.3456 → 22.3)

Theme.of(context).brightness

实时获取当前主题亮度,用于动态着色


六、扩展与应用场景

可扩展方向
  • 历史记录:保存多次计算结果,生成趋势图;
  • 单位切换:支持英制(英寸/磅);
  • 年龄/性别适配:结合 WHO 或中国标准提供更精准评估;
  • 健康目标设定:输入目标 BMI,反推所需体重;
  • 分享功能:将结果生成图片分享给朋友或医生。
应用场景
  • 健康类 App 子模块:集成到健身、饮食或医疗应用中;
  • 学校/社区健康筛查:快速评估群体 BMI 分布;
  • 个人健康管理:日常监测体重变化趋势。

七、结语:小工具,大关怀

这个 BMI 计算器虽功能简单,却处处体现对用户的尊重与关怀

  • 用颜色传递情绪(绿色鼓励,红色警示);
  • 用语言提供建议(而非冷冰冰的数字);
  • 用主题适配习惯(亮暗自如切换);
  • 用动画提升愉悦感(结果优雅呈现)。 完整代码
代码语言:javascript
复制
import 'package:flutter/material.dart';

void main() {
  runApp(const BmiApp());
}

class BmiApp extends StatelessWidget {
  const BmiApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: '📏 BMI 计算器',
      theme: ThemeData(
        brightness: Brightness.light,
        primarySwatch: Colors.blue,
        scaffoldBackgroundColor: Colors.grey[50],
        inputDecorationTheme: const InputDecorationTheme(
          filled: true,
          fillColor: Colors.white,
          border: OutlineInputBorder(
            borderRadius: BorderRadius.all(Radius.circular(12)),
            borderSide: BorderSide.none,
          ),
        ),
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        primarySwatch: Colors.blue,
        scaffoldBackgroundColor: const Color(0xFF121212),
        inputDecorationTheme: const InputDecorationTheme(
          filled: true,
          fillColor: Color(0xFF1E1E1E),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.all(Radius.circular(12)),
            borderSide: BorderSide.none,
          ),
        ),
      ),
      themeMode: ThemeMode.system,
      home: const BmiCalculatorScreen(),
    );
  }
}

class BmiCalculatorScreen extends StatefulWidget {
  const BmiCalculatorScreen({super.key});

  @override
  State<BmiCalculatorScreen> createState() => _BmiCalculatorScreenState();
}

class _BmiCalculatorScreenState extends State<BmiCalculatorScreen> {
  final TextEditingController _heightController = TextEditingController();
  final TextEditingController _weightController = TextEditingController();

  double? _bmi;
  String _interpretation = '';
  Color _resultColor = Colors.blue;

  void _calculateBMI() {
    final heightStr = _heightController.text.trim();
    final weightStr = _weightController.text.trim();

    if (heightStr.isEmpty || weightStr.isEmpty) {
      _showError('请输入身高和体重');
      return;
    }

    final height = double.tryParse(heightStr);
    final weight = double.tryParse(weightStr);

    if (height == null || weight == null || height <= 0 || weight <= 0) {
      _showError('请输入有效的正数');
      return;
    }

    // 身高从 cm 转为 m
    final heightInMeters = height / 100;
    final bmi = weight / (heightInMeters * heightInMeters);

    String interpretation;
    Color color;

    if (bmi < 18.5) {
      interpretation = '偏瘦\n建议增加营养摄入,保持规律作息。';
      color = Colors.orange;
    } else if (bmi < 24) {
      interpretation = '正常\n继续保持健康的生活方式!';
      color = Colors.green;
    } else if (bmi < 28) {
      interpretation = '超重\n建议适当运动,控制饮食。';
      color = Colors.orangeAccent;
    } else {
      interpretation = '肥胖\n建议咨询医生,制定科学减重计划。';
      color = Colors.red;
    }

    setState(() {
      _bmi = bmi;
      _interpretation = interpretation;
      _resultColor = color;
    });
  }

  void _showError(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  void _reset() {
    _heightController.clear();
    _weightController.clear();
    setState(() {
      _bmi = null;
      _interpretation = '';
      _resultColor = Colors.blue;
    });
  }

  @override
  Widget build(BuildContext context) {
    final isDark = Theme.of(context).brightness == Brightness.dark;
    final textColor = isDark ? Colors.white : Colors.black87;
    final cardColor = isDark ? const Color(0xFF1E1E1E) : Colors.white;

    return Scaffold(
      appBar: AppBar(
        title: const Text('BMI 健康计算器'),
        centerTitle: true,
        backgroundColor: Colors.transparent,
        elevation: 0,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // 输入区域
            TextField(
              controller: _heightController,
              keyboardType: TextInputType.numberWithOptions(decimal: true),
              decoration: InputDecoration(
                labelText: '身高 (cm)',
                hintText: '例如:175',
                prefixIcon: const Icon(Icons.height),
              ),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: _weightController,
              keyboardType: TextInputType.numberWithOptions(decimal: true),
              decoration: InputDecoration(
                labelText: '体重 (kg)',
                hintText: '例如:70',
                prefixIcon: const Icon(Icons.monitor_weight),
              ),
            ),
            const SizedBox(height: 24),

            // 计算按钮
            ElevatedButton.icon(
              onPressed: _calculateBMI,
              icon: const Icon(Icons.calculate),
              label: const Text('计算 BMI', style: TextStyle(fontSize: 18)),
              style: ElevatedButton.styleFrom(
                padding: const EdgeInsets.symmetric(vertical: 16),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(12),
                ),
              ),
            ),
            const SizedBox(height: 16),

            // 重置按钮(轻量)
            TextButton(
              onPressed: _reset,
              child: const Text('重置', style: TextStyle(fontSize: 16)),
            ),

            const SizedBox(height: 32),

            // 结果区域
            if (_bmi != null)
              AnimatedContainer(
                duration: const Duration(milliseconds: 500),
                curve: Curves.easeOut,
                child: Card(
                  color: cardColor,
                  elevation: 4,
                  child: Padding(
                    padding: const EdgeInsets.all(24),
                    child: Column(
                      children: [
                        Text(
                          '你的 BMI',
                          style: TextStyle(
                            fontSize: 18,
                            color: Colors.grey[600] ?? Colors.grey,
                          ),
                        ),
                        const SizedBox(height: 8),
                        Text(
                          _bmi!.toStringAsFixed(1),
                          style: TextStyle(
                            fontSize: 48,
                            fontWeight: FontWeight.bold,
                            color: _resultColor,
                          ),
                        ),
                        const SizedBox(height: 16),
                        Text(
                          _interpretation,
                          textAlign: TextAlign.center,
                          style: TextStyle(
                            fontSize: 18,
                            height: 1.5,
                            color: textColor,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            if (_bmi == null)
              Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    const Icon(Icons.monitor_heart,
                        size: 80, color: Colors.grey),
                    const SizedBox(height: 16),
                    Text(
                      '输入身高和体重\n开始计算你的 BMI',
                      textAlign: TextAlign.center,
                      style: TextStyle(
                        fontSize: 18,
                        color: isDark ? Colors.grey[500] : Colors.grey[600],
                      ),
                    ),
                  ],
                ),
              ),
          ],
        ),
      ),
    );
  }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-02-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Flutter for OpenHarmony BMI 健康计算器:打造支持深色模式的智能健康工具
    • 一、核心功能:从输入到健康建议
    • 二、智能输入处理与错误反馈
      • 1. 安全解析用户输入
      • 2. 即时错误提示
    • 三、双主题系统:自动适配用户偏好
      • 1. 主题定义
      • 2. 动态颜色获取
    • 四、UI/UX 设计亮点
      • 1. 输入体验优化
      • 2. 按钮层级分明
      • 3. 结果区域动态呈现
      • 4. 平滑动画过渡
    • 五、技术实现细节
    • 六、扩展与应用场景
      • 可扩展方向
      • 应用场景
    • 七、结语:小工具,大关怀
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档