当前位置: 首页 > news >正文

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 手机模拟器
  • 适配平台:OpenHarmony

2. 依赖配置

无需修改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 State<MainPage> createState() => _MainPageState(); } class _MainPageState extends State<MainPage> { int _currentIndex = 0; final List<Widget> _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 State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { 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 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 _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))), ); } }

📱 调试与运行完整步骤

  1. 停止上一个项目运行
  2. 无需修改pubspec.yaml依赖,保持已有配置即可
  3. 打开lib/main.dart,修改原有代码,上方有完整代码
  4. 终端执行运行命令:
    flutter run
  5. 选择 OpenHarmony 模拟器编译运行
  6. 进入健康录入页,填写完整信息并保存
  7. 切换回首页,自动加载并卡片展示个人健康数据
  8. 修改录入信息重新保存,点击首页刷新按钮可实时更新

🔐 跨平台适配说明

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

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

🧩 常见错误排查

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

🎨 项目后续规划

Day6 已完成首页读取本地数据并卡片美化展示,后续按 24 天规划继续推进:

  • Day7:个人中心页面回显全部健康信息
  • Day8:新增清空本地数据、重置表单功能
  • Day9:表单输入范围合法性校验优化
  • 后续依次完成 BMI 计算、图表可视化、UI 全局美化、启动页、项目打包等功能

📌 项目总结

本篇完整记录了 Flutter 鸿蒙健康管理项目 Day6 开发全过程,实现了首页利用生命周期自动读取shared_preferences本地存储数据,通过 Card 卡片布局美化 UI,封装通用条目组件简化代码,同时支持手动刷新最新数据。既巩固了本地存储读写、页面生命周期、组件封装等知识点,又完成了界面美化与交互优化,为后续个人中心回显、数据重置等功能打下坚实基础。

✅ 结尾小贴士

  • 必须在真机 / 鸿蒙模拟器运行,Web 端本地存储逻辑异常不生效
  • 每次录入新数据保存后,回到首页点刷新即可看到最新内容
  • 代码可直接全覆盖使用,无需改动任何配置,一键运行即可出效果
  • 点赞收藏不迷路,后续每日开发笔记持续同步更新!
http://www.jsqmd.com/news/794379/

相关文章:

  • Yeti性能优化技巧:10个方法提升威胁情报处理效率
  • B+树、、
  • 基于Vue 3与JSON数据构建MBTI运势生成器:前端实战开发指南
  • 【Hermes:实战场景】36、Hermes Agent + Home Assistant 集成全攻略:让 AI 替你控制全屋智能
  • 【信息科学与工程学】【人工智能】【数字孪生】【游戏科学】主要数学模型-第九篇 计算神经科学
  • 如何快速解密网易云音乐NCM文件:5步完成格式转换的完整指南
  • 智能高效:Seraphine英雄联盟辅助工具终极使用指南
  • 孤舟笔记 IO 与网络编程篇四 IO多路复用到底是什么?select/poll/epoll一篇搞懂
  • 把轻量接口做成真正可用的业务入口,聊透 ABAP HTTP Service Editor 的开发节奏
  • TVA与RV协同赋能具身机器人运动控制(3)
  • 向华为学习——解读华为流程型组织的基石:业务流架构(BPA)全景解析【附全文阅读】
  • CANN/asc-devkit向量构造函数
  • [具身智能-659]:ROS2 与人类大脑神经系统 完整类比 + 异同对比总结
  • Starknet智能体开发:构建安全自主的链上AI代理基础设施
  • 从 Classic ABAP 走到 ABAP Cloud,开发习惯、架构边界与 Clean Core 的重新建立
  • 告别网盘限速!3步搞定百度网盘高速下载秘籍
  • 别把 `TTFT`、`TPOT`、吞吐量都当成“延迟优化”:真正先分开的,是排队、prefill、decode、continuous batching 这 4 层
  • Java基础——抽象类与接口
  • 谱域图算子与边缘计算优化实践
  • Java 判断选择循环
  • Agent Framework 中智能体的Concurrent编排模式
  • 《Java 100 天进阶之路》第1篇:编程语言类型有哪些?我心中的TOP1编程语言,什么是Java跨平台性?
  • JDBC实现数据库增删改查
  • Cursor智能体开发:Agent 模式
  • 把边界立起来,理解 ABAP Cloud 的几根主梁
  • LangChain详解
  • SpringBoot的服装商城系统毕设源码
  • Unity路网建模踩坑实录:OpenDRIVE解析中那些“反直觉”的几何参数(hdg, curvature到底怎么算?)
  • 渗透测试技巧(七)| 系统提权
  • 从 CDS 到服务契约,读懂 ABAP Cloud 的 Model-Driven Architecture