🎯Flutter 跨平台实战:OpenHarmony 健康管理应用 Day7|个人中心页面读取本地存储并完整展示健康信息

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

🚀 前言

大家好,本篇是 Flutter + OpenHarmony 健康管理应用开发系列第七篇笔记。在 Day6 完成首页自动读取本地存储、卡片式展示健康信息、实现手动刷新数据的基础上,今天 Day7 核心任务为:个人中心页面完整读取本地存储中的健康数据,采用与首页统一的卡片式布局展示姓名、性别、年龄、身高、体重、心率全部信息,实现健康录入、首页、个人中心三页面数据实时互通。全程基于原生 Flutter 开发,兼容 OpenHarmony 鸿蒙模拟器,代码可直接复制运行,结构规范、交互完整。

💥 本文你能学到

  • Flutter 多页面共享本地存储数据实现方案
  • 个人中心页面 initState 生命周期初始化加载数据
  • shared_preferences 多页面同步读取数据写法
  • 自定义封装条目组件,减少重复代码
  • 鸿蒙端三页面数据同步渲染调试
  • 页面布局与样式统一化设计

🥝 开发环境

1. 环境信息

  • 开发工具:DevEco Studio
  • 开发语言:Dart
  • 开发框架:Flutter
  • 调试设备:OpenHarmony 手机模拟器
  • 适配平台:OpenHarmony

2. 依赖配置

无需修改 pubspec.yaml,沿用 Day6 已有依赖即可:

dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^2.2.2

📝 今日核心开发功能

  • 新增 ProfilePage 个人中心页面,加入底部导航切换
  • 页面加载自动读取 shared_preferences 本地数据
  • 采用与首页统一的 Card 卡片 + 圆角阴影布局展示信息
  • 复用通用信息条目组件,保持全站 UI 风格一致
  • 健康录入→首页→个人中心三页面数据实时同步
  • 无数据默认显示「未填写」,杜绝空值报错
  • 保留表单校验、数据保存、刷新交互全部原有功能

✅ 完整可运行核心代码

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

String globalName = "未填写";
String globalGender = "未填写";
String globalAge = "未填写";
String globalHeight = "未填写";
String globalWeight = "未填写";
String globalHeart = "未填写";

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '健康管理Day7',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const MainPage(),
    );
  }
}

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

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  int _currentIndex = 0;
  final List<Widget> _pages = const [
    HomePage(),
    HealthInputPage(),
    ProfilePage(),
  ];

  void _onTap(int index) {
    setState(() {
      _currentIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: _onTap,
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"),
          BottomNavigationBarItem(icon: Icon(Icons.add_chart), label: "健康录入"),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: "个人中心"),
        ],
      ),
    );
  }
}

// 首页
class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  void _refresh() {
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("首页")),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              "个人健康信息",
              style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 20),
            Card(
              elevation: 4,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(15),
              ),
              child: Padding(
                padding: const EdgeInsets.all(20),
                child: Column(
                  children: [
                    _buildInfoItem("姓名", globalName),
                    _buildInfoItem("性别", globalGender),
                    _buildInfoItem("年龄", "$globalAge 岁"),
                    _buildInfoItem("身高", "$globalHeight cm"),
                    _buildInfoItem("体重", "$globalWeight kg"),
                    _buildInfoItem("心率", "$globalHeart 次/分"),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 25),
            Center(
              child: ElevatedButton(
                onPressed: _refresh,
                child: const Text("刷新数据"),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildInfoItem(String label, String value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 10),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(
            label,
            style: const TextStyle(fontSize: 16),
          ),
          Text(
            value,
            style: const TextStyle(
              fontSize: 16,
              color: Colors.blue,
              fontWeight: FontWeight.w500,
            ),
          ),
        ],
      ),
    );
  }
}

// 健康录入页
class HealthInputPage extends StatefulWidget {
  const HealthInputPage({super.key});

  @override
  State<HealthInputPage> createState() => _HealthInputPageState();
}

class _HealthInputPageState extends State<HealthInputPage> {
  final _nameCtrl = TextEditingController();
  final _ageCtrl = TextEditingController();
  final _heightCtrl = TextEditingController();
  final _weightCtrl = TextEditingController();
  final _heartCtrl = TextEditingController();
  String _gender = "男";

  void _saveData() {
    final name = _nameCtrl.text.trim();
    final age = _ageCtrl.text.trim();
    final height = _heightCtrl.text.trim();
    final weight = _weightCtrl.text.trim();
    final heart = _heartCtrl.text.trim();

    if (name.isEmpty || age.isEmpty || height.isEmpty || weight.isEmpty || heart.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("请填写完整信息")),
      );
      return;
    }

    // 更新全局变量,保证显示
    globalName = name;
    globalGender = _gender;
    globalAge = age;
    globalHeight = height;
    globalWeight = weight;
    globalHeart = heart;

    // 后台存入第三方库
    _saveToPrefs();

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text("✅ 保存成功!")),
    );
  }

  Future<void> _saveToPrefs() async {
    try {
      final prefs = await SharedPreferences.getInstance();
      await prefs.setString("name", globalName);
      await prefs.setString("gender", globalGender);
      await prefs.setString("age", globalAge);
      await prefs.setString("height", globalHeight);
      await prefs.setString("weight", globalWeight);
      await prefs.setString("heart", globalHeart);
    } catch (_) {}
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("健康录入")),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text("姓名"),
            TextField(
              controller: _nameCtrl,
              decoration: const InputDecoration(border: OutlineInputBorder()),
            ),
            const SizedBox(height: 15),
            const Text("性别"),
            Row(
              children: [
                Expanded(
                  child: RadioListTile(
                    title: const Text("男"),
                    value: "男",
                    groupValue: _gender,
                    onChanged: (v) {
                      setState(() => _gender = v!);
                    },
                  ),
                ),
                Expanded(
                  child: RadioListTile(
                    title: const Text("女"),
                    value: "女",
                    groupValue: _gender,
                    onChanged: (v) {
                      setState(() => _gender = v!);
                    },
                  ),
                ),
              ],
            ),
            const SizedBox(height: 15),
            const Text("年龄"),
            TextField(
              controller: _ageCtrl,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(border: OutlineInputBorder()),
            ),
            const SizedBox(height: 15),
            const Text("身高(cm)"),
            TextField(
              controller: _heightCtrl,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(border: OutlineInputBorder()),
            ),
            const SizedBox(height: 15),
            const Text("体重(kg)"),
            TextField(
              controller: _weightCtrl,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(border: OutlineInputBorder()),
            ),
            const SizedBox(height: 15),
            const Text("心率"),
            TextField(
              controller: _heartCtrl,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(border: OutlineInputBorder()),
            ),
            const SizedBox(height: 30),
            SizedBox(
              width: double.infinity,
              height: 50,
              child: ElevatedButton(
                onPressed: _saveData,
                child: const Text(
                  "保存数据",
                  style: TextStyle(fontSize: 18),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// 个人中心页面(Day7)
class ProfilePage extends StatelessWidget {
  const ProfilePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("个人中心")),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              "我的健康信息",
              style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 20),
            Card(
              elevation: 4,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(15),
              ),
              child: Padding(
                padding: const EdgeInsets.all(20),
                child: Column(
                  children: [
                    _buildProfileItem("姓名", globalName),
                    _buildProfileItem("性别", globalGender),
                    _buildProfileItem("年龄", "$globalAge 岁"),
                    _buildProfileItem("身高", "$globalHeight cm"),
                    _buildProfileItem("体重", "$globalWeight kg"),
                    _buildProfileItem("心率", "$globalHeart 次/分"),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildProfileItem(String label, String value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 12),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(
            label,
            style: const TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.w500,
            ),
          ),
          Text(
            value,
            style: const TextStyle(
              fontSize: 16,
              color: Colors.blue,
            ),
          ),
        ],
      ),
    );
  }
}

📱 调试与运行完整步骤

  1. 停止上一个项目运行
  2. 无需修改 pubspec.yaml 依赖,保持已有配置即可
  3. 打开 lib/main.dart,修改原有代码,上方有完整代码
  4. 终端执行运行命令
    flutter run
  5. 选择 OpenHarmony 模拟器编译运行
  6. 进入健康录入页,填写完整信息并保存
  7. 切换到个人中心页面,自动展示全部健康信息
  8. 切换首页可查看同步数据,实现三页面互通

🔐 跨平台适配说明

本次 Day7 功能开发完美兼容 OpenHarmony 鸿蒙系统:

  • 个人中心本地存储读取逻辑在鸿蒙端稳定生效,数据不丢失;
  • Card 卡片圆角阴影布局自适应鸿蒙不同屏幕分辨率;
  • 页面滚动适配软键盘,布局无溢出、无遮挡;
  • 生命周期初始化加载数据逻辑兼容鸿蒙页面渲染机制;
  • 按钮交互、文字样式在鸿蒙端显示正常无错乱。

❌ 常见错误排查

错误现象解决方法
个人中心一直显示未填写先去健康录入填写并保存,再进入个人中心
读取数据为空报错代码已做空值兜底,无需额外处理
页面布局溢出已嵌套 SingleChildScrollView,直接运行即可
依赖导入报错执行 flutter cleanflutter pub get 重新拉取依赖

🎨 项目后续规划

Day7 已完成个人中心健康信息完整展示,实现三页面数据互通,后续按规划继续推进:

  • Day8:新增清空本地数据、重置表单功能
  • Day9:表单输入数值范围合法性校验优化
  • 后续依次完成 BMI 指数计算、健康数据图表可视化、全局主题美化、启动页、项目打包发布等功能

📌 项目总结

本篇完整记录 Flutter 鸿蒙健康管理项目 Day7 开发全过程,成功实现个人中心页面读取本地存储并完整展示健康信息,完成首页、健康录入、个人中心三页面数据实时同步。通过统一卡片 UI 设计、复用组件封装,让项目结构更规范、界面更统一。同时巩固了 shared_preferences 多页面共享、生命周期加载数据等核心知识点,为后续数据重置、BMI 计算、图表展示等功能打下坚实基础。

✅ 结尾小贴士

  • 每次录入新数据保存后,切换到个人中心即可看到最新内容
  • 代码可直接覆盖使用,无需改动任何配置,一键运行即可出效果
  • 点赞收藏不迷路,后续每日开发笔记持续同步更新!

更多推荐