Flutter 跨平台实战:OpenHarmony 健康管理应用 Day7|个人中心页面读取本地存储并完整展示健康信息
·
🎯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,
),
),
],
),
);
}
}
📱 调试与运行完整步骤
- 停止上一个项目运行
- 无需修改
pubspec.yaml依赖,保持已有配置即可 - 打开
lib/main.dart,修改原有代码,上方有完整代码 - 终端执行运行命令
flutter run - 选择 OpenHarmony 模拟器编译运行
- 进入健康录入页,填写完整信息并保存

- 切换到个人中心页面,自动展示全部健康信息

- 切换首页可查看同步数据,实现三页面互通

🔐 跨平台适配说明
本次 Day7 功能开发完美兼容 OpenHarmony 鸿蒙系统:
- 个人中心本地存储读取逻辑在鸿蒙端稳定生效,数据不丢失;
- Card 卡片圆角阴影布局自适应鸿蒙不同屏幕分辨率;
- 页面滚动适配软键盘,布局无溢出、无遮挡;
- 生命周期初始化加载数据逻辑兼容鸿蒙页面渲染机制;
- 按钮交互、文字样式在鸿蒙端显示正常无错乱。
❌ 常见错误排查
| 错误现象 | 解决方法 |
|---|---|
| 个人中心一直显示未填写 | 先去健康录入填写并保存,再进入个人中心 |
| 读取数据为空报错 | 代码已做空值兜底,无需额外处理 |
| 页面布局溢出 | 已嵌套 SingleChildScrollView,直接运行即可 |
| 依赖导入报错 | 执行 flutter clean → flutter pub get 重新拉取依赖 |
🎨 项目后续规划
Day7 已完成个人中心健康信息完整展示,实现三页面数据互通,后续按规划继续推进:
- Day8:新增清空本地数据、重置表单功能
- Day9:表单输入数值范围合法性校验优化
- 后续依次完成 BMI 指数计算、健康数据图表可视化、全局主题美化、启动页、项目打包发布等功能
📌 项目总结
本篇完整记录 Flutter 鸿蒙健康管理项目 Day7 开发全过程,成功实现个人中心页面读取本地存储并完整展示健康信息,完成首页、健康录入、个人中心三页面数据实时同步。通过统一卡片 UI 设计、复用组件封装,让项目结构更规范、界面更统一。同时巩固了 shared_preferences 多页面共享、生命周期加载数据等核心知识点,为后续数据重置、BMI 计算、图表展示等功能打下坚实基础。
✅ 结尾小贴士
- 每次录入新数据保存后,切换到个人中心即可看到最新内容
- 代码可直接覆盖使用,无需改动任何配置,一键运行即可出效果
- 点赞收藏不迷路,后续每日开发笔记持续同步更新!
更多推荐
所有评论(0)