Flutter 跨平台实战:OpenHarmony 健康管理应用 Day6|首页读取本地存储并卡片展示个人健康信息
Flutter 跨平台实战OpenHarmony 健康管理应用 Day6首页读取本地存储并卡片展示个人健康信息欢迎加入开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.net 前言大家好本篇是Flutter OpenHarmony 健康管理应用开发系列第六篇笔记。在 Day5 完成引入shared_preferences依赖、新增性别单选功能并将全部健康信息持久化保存到本地的基础上今天 Day6 核心任务为首页自动读取本地存储中的健康数据采用卡片式布局优雅展示姓名、性别、年龄、身高、体重、心率等信息同时提供手动刷新按钮实现页面数据动态更新。全程基于原生 Flutter 开发兼容 OpenHarmony 鸿蒙模拟器代码可直接复制运行结构规范、交互完整适合课程实训作业提交与高分自查。 本文你能学到Flutter 页面初始化initState生命周期使用shared_preferences读取本地存储数据写法空数据默认值兜底处理避免空指针报错Card 卡片组件、圆角阴影布局美化自定义封装条目组件简化重复代码按钮手动刷新本地数据交互实现鸿蒙端页面布局适配与数据渲染调试 开发环境1. 环境信息开发工具DevEco Studio开发语言Dart开发框架Flutter调试设备OpenHarmony 手机模拟器适配平台OpenHarmony2. 依赖配置无需修改pubspec.yaml沿用 Day5 已有依赖即可dependencies: flutter: sdk: flutter shared_preferences: ^2.2.2 今日核心开发功能首页改为有状态组件页面加载自动触发读取本地数据从shared_preferences读取姓名、性别、年龄、身高、体重、心率无数据时默认显示「未填写」做友好兜底使用 Card 卡片 圆角阴影进行 UI 美化布局封装通用信息条目组件统一样式、减少冗余代码添加刷新按钮手动重新加载本地最新数据保留健康录入页原有保存、性别选择、表单校验完整功能底部导航三页面正常切换互不影响✅ 完整可运行核心代码import package:flutter/material.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: 健康管理, debugShowCheckedModeBanner: false, theme: ThemeData(primarySwatch: Colors.blue), home: const MainPage(), ); } } class MainPage extends StatefulWidget { const MainPage({super.key}); override StateMainPage createState() _MainPageState(); } class _MainPageState extends StateMainPage { int _currentIndex 0; final ListWidget _pages const [ HomePage(), HealthInputPage(), ProfilePage(), ]; void _onItemTapped(int index) { setState(() { _currentIndex index; }); } override Widget build(BuildContext context) { return Scaffold( body: _pages[_currentIndex], bottomNavigationBar: BottomNavigationBar( currentIndex: _currentIndex, onTap: _onItemTapped, 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 StateHomePage createState() _HomePageState(); } class _HomePageState extends StateHomePage { String name globalName; String gender globalGender; String age globalAge; String height globalHeight; String weight globalWeight; String heart globalHeart; void _refresh() { setState(() { name globalName; gender globalGender; age globalAge; height globalHeight; weight globalWeight; heart globalHeart; }); } 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: 5, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), child: Padding( padding: const EdgeInsets.all(20), child: Column( children: [ _item(姓名, name), _item(性别, gender), _item(年龄, $age 岁), _item(身高, $height cm), _item(体重, $weight kg), _item(心率, $heart 次/分), ], ), ), ), const SizedBox(height: 30), Center( child: ElevatedButton( onPressed: _refresh, child: const Text(刷新数据), ), ), ], ), ), ); } Widget _item(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 StateHealthInputPage createState() _HealthInputPageState(); } class _HealthInputPageState extends StateHealthInputPage { final _nameCtrl TextEditingController(); final _ageCtrl TextEditingController(); final _heightCtrl TextEditingController(); final _weightCtrl TextEditingController(); final _heartCtrl TextEditingController(); String _gender 男; void _save() { 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; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text(✅ 数据保存成功)), ); } 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: 16, fontWeight: FontWeight.w500)), const SizedBox(height: 8), TextField(controller: _nameCtrl, decoration: const InputDecoration(border: OutlineInputBorder())), const SizedBox(height: 16), const Text(性别, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), Row( children: [ Expanded( child: ListTile( title: const Text(男), leading: Radio( value: 男, groupValue: _gender, onChanged: (v) setState(() _gender v!), ), ), ), Expanded( child: ListTile( title: const Text(女), leading: Radio( value: 女, groupValue: _gender, onChanged: (v) setState(() _gender v!), ), ), ), ], ), const SizedBox(height: 16), const Text(年龄, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), const SizedBox(height: 8), TextField(controller: _ageCtrl, keyboardType: TextInputType.number, decoration: const InputDecoration(border: OutlineInputBorder())), const SizedBox(height: 16), const Text(身高(cm), style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), const SizedBox(height: 8), TextField(controller: _heightCtrl, keyboardType: TextInputType.number, decoration: const InputDecoration(border: OutlineInputBorder())), const SizedBox(height: 16), const Text(体重(kg), style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), const SizedBox(height: 8), TextField(controller: _weightCtrl, keyboardType: TextInputType.number, decoration: const InputDecoration(border: OutlineInputBorder())), const SizedBox(height: 16), const Text(心率, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), const SizedBox(height: 8), TextField(controller: _heartCtrl, keyboardType: TextInputType.number, decoration: const InputDecoration(border: OutlineInputBorder())), const SizedBox(height: 30), SizedBox( width: double.infinity, height: 50, child: ElevatedButton( onPressed: _save, child: const Text(保存数据, style: TextStyle(fontSize: 18)), ), ), ], ), ), ); } } // 个人中心 class ProfilePage extends StatelessWidget { const ProfilePage({super.key}); override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text(个人中心)), body: const Center(child: Text(个人中心, style: TextStyle(fontSize: 18))), ); } } 调试与运行完整步骤停止上一个项目运行无需修改pubspec.yaml依赖保持已有配置即可打开lib/main.dart修改原有代码上方有完整代码终端执行运行命令flutter run选择 OpenHarmony 模拟器编译运行进入健康录入页填写完整信息并保存切换回首页自动加载并卡片展示个人健康数据修改录入信息重新保存点击首页刷新按钮可实时更新 跨平台适配说明本次 Day6 功能开发完美兼容 OpenHarmony 鸿蒙系统本地存储读取逻辑在鸿蒙端稳定生效数据不丢失Card 卡片圆角阴影布局自适应鸿蒙不同屏幕分辨率页面滚动适配软键盘布局无溢出、无遮挡生命周期初始化加载数据逻辑兼容鸿蒙页面渲染机制按钮交互、文字样式在鸿蒙端显示正常无错乱。 常见错误排查错误现象解决方法首页一直显示未填写先去健康录入填写并保存再刷新首页数据读取数据为空报错代码已加空值兜底无需额外处理页面布局溢出已嵌套 SingleChildScrollView直接运行即可依赖导入报错执行flutter clean-flutter pub get重新拉取依赖 项目后续规划Day6 已完成首页读取本地数据并卡片美化展示后续按 24 天规划继续推进Day7个人中心页面回显全部健康信息Day8新增清空本地数据、重置表单功能Day9表单输入范围合法性校验优化后续依次完成 BMI 计算、图表可视化、UI 全局美化、启动页、项目打包等功能 项目总结本篇完整记录了 Flutter 鸿蒙健康管理项目 Day6 开发全过程实现了首页利用生命周期自动读取shared_preferences本地存储数据通过 Card 卡片布局美化 UI封装通用条目组件简化代码同时支持手动刷新最新数据。既巩固了本地存储读写、页面生命周期、组件封装等知识点又完成了界面美化与交互优化为后续个人中心回显、数据重置等功能打下坚实基础。✅ 结尾小贴士必须在真机 / 鸿蒙模拟器运行Web 端本地存储逻辑异常不生效每次录入新数据保存后回到首页点刷新即可看到最新内容代码可直接全覆盖使用无需改动任何配置一键运行即可出效果点赞收藏不迷路后续每日开发笔记持续同步更新