flutter_for_openharmony口腔护理app实战+记录实现
口腔护理App记录页面摘要(150字): 记录页面整合日历视图与护理历史,帮助用户追踪刷牙、漱口等习惯。采用Flutter框架构建,使用TableCalendar组件展示可交互日历,支持周/月视图切换。页面分为日历区、分类导航和当日记录三部分,通过Provider管理状态实现数据实时同步。日历标记有记录日期,点击可查看详情;分类卡片导航至各类型记录页面;当日记录区域展示最新护理数据。该功能通过可视

记录页面是口腔护理App的核心功能之一,用户可以在这里查看自己的护理历史,包括刷牙、漱口、牙线使用等各类记录。这个页面整合了日历视图和记录列表,让用户能够直观地了解自己的口腔护理习惯。
为什么需要记录功能
养成良好的口腔护理习惯需要持续的坚持和反馈。通过记录功能,用户可以清楚地看到自己每天的护理情况,哪些天坚持得好,哪些天有所懈怠。这种可视化的反馈机制能够有效激励用户保持良好习惯。记录功能还能帮助用户在就医时向医生展示自己的护理历史,便于医生做出更准确的诊断。
引入必要的依赖
import 'package:flutter/material.dart';
Flutter核心库提供了构建UI所需的所有基础组件。
Material Design风格的控件让应用看起来更加专业。
import 'package:provider/provider.dart';
Provider是Flutter官方推荐的状态管理方案,轻量且易于使用。
它让我们能够在整个应用中共享护理记录数据,任何地方添加的记录都能实时同步到这个页面。
import 'package:table_calendar/table_calendar.dart';
table_calendar是一个功能强大的日历组件库。
支持周视图、月视图切换,还能在日期上显示事件标记,非常适合记录类应用。
import 'package:intl/intl.dart';
intl库用于日期时间的格式化显示。
比如把DateTime对象格式化成"08:30"这样的字符串,方便用户阅读。
import '../../providers/app_provider.dart';
AppProvider是我们自定义的状态管理类。
它存储了所有护理记录数据,包括刷牙、漱口、牙线等各类记录。
import '../pages/brush_history_page.dart';
import '../pages/mouthwash_page.dart';
import '../pages/floss_page.dart';
这些是各个记录详情页面的导入。
点击分类卡片后会跳转到对应页面查看详细记录。
import '../pages/diet_record_page.dart';
import '../pages/oral_check_page.dart';
import '../pages/oral_issue_page.dart';
饮食记录、检查记录、口腔问题页面的导入。
这些页面提供了更专业的记录管理功能。
使用StatefulWidget管理状态
记录页面需要管理日历的选中状态和显示格式,所以使用StatefulWidget。
class RecordTab extends StatefulWidget {
const RecordTab({super.key});
State<RecordTab> createState() => _RecordTabState();
}
StatefulWidget适合需要维护本地UI状态的场景。
createState方法返回State对象,Flutter会在需要时调用它。
class _RecordTabState extends State<RecordTab> {
DateTime _focusedDay = DateTime.now();
_focusedDay表示日历当前聚焦的日期。
初始化为今天,用户打开页面时默认显示当前月份。
DateTime? _selectedDay;
_selectedDay是用户点击选中的日期。
使用可空类型是因为初始状态下可能没有选中任何日期。
CalendarFormat _calendarFormat = CalendarFormat.week;
_calendarFormat控制日历显示为周视图还是月视图。
默认显示周视图,更紧凑,适合日常查看。
页面整体布局结构
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('护理记录'),
),
Scaffold提供了标准的Material Design页面结构。
AppBar显示页面标题"护理记录",让用户知道当前所在位置。
body: Consumer<AppProvider>(
builder: (context, provider, _) {
Consumer监听AppProvider的数据变化。
当有新记录添加时,builder会自动重新执行,页面随之刷新。
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
SingleChildScrollView让整个页面可以滚动。
Column垂直排列各个模块,crossAxisAlignment设为start让内容左对齐。
children: [
_buildCalendar(provider),
日历组件放在最上方,是这个页面的核心视觉元素。
传入provider是为了获取记录数据,在日历上标记有记录的日期。
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
下方内容用Padding包裹,添加16像素的内边距。
嵌套的Column用于垂直排列护理记录分类和今日记录。
const Text('护理记录', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
_buildRecordCategories(context),
"护理记录"标题使用18像素加粗字体。
下方是分类网格,展示六种护理记录类型。
const SizedBox(height: 20),
const Text('今日记录', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
_buildTodayRecords(provider),
],
),
),
],
),
);
},
),
);
}
"今日记录"区域展示当天的所有护理记录。
模块之间用SizedBox添加间距,让布局更有层次感。
日历组件的实现细节
日历是记录页面的亮点功能,用户可以通过日历查看历史记录。
Widget _buildCalendar(AppProvider provider) {
return Container(
margin: const EdgeInsets.all(16),
日历外层用Container包裹,四周添加16像素外边距。
这样日历不会紧贴屏幕边缘,视觉上更舒适。
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [BoxShadow(color: Colors.grey.shade200, blurRadius: 10)],
),
白色背景让日历在灰色页面背景上更加突出。
16像素圆角和浅色阴影形成卡片效果,符合现代UI设计趋势。
child: TableCalendar(
firstDay: DateTime.utc(2020, 1, 1),
lastDay: DateTime.utc(2030, 12, 31),
TableCalendar需要设置日期范围。
firstDay和lastDay定义了用户可以浏览的日期边界,这里设置了10年的范围。
focusedDay: _focusedDay,
calendarFormat: _calendarFormat,
focusedDay决定日历当前显示哪个月份。
calendarFormat控制显示周视图还是月视图。
selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
selectedDayPredicate是一个判断函数。
isSameDay是table_calendar提供的工具函数,用于比较两个日期是否是同一天。
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
},
用户点击某天时触发onDaySelected回调。
通过setState更新状态,日历会重新渲染显示选中效果。
onFormatChanged: (format) {
setState(() {
_calendarFormat = format;
});
},
用户点击格式切换按钮时触发这个回调。
可以在周视图和月视图之间切换,满足不同的查看需求。
日历样式配置
calendarStyle: const CalendarStyle(
selectedDecoration: BoxDecoration(
color: Color(0xFF26A69A),
shape: BoxShape.circle,
),
selectedDecoration定义选中日期的样式。
使用主题绿色圆形背景,与整体设计风格保持一致。
todayDecoration: BoxDecoration(
color: Color(0xFF80CBC4),
shape: BoxShape.circle,
),
),
todayDecoration定义今天日期的样式。
使用稍浅的绿色,让用户能区分"今天"和"选中的日期"。
headerStyle: const HeaderStyle(
formatButtonVisible: true,
titleCentered: true,
),
headerStyle配置日历头部样式。
formatButtonVisible显示格式切换按钮,titleCentered让月份标题居中显示。
日历事件标记功能
在日历上标记有记录的日期,让用户一眼看出哪些天有护理记录。
eventLoader: (day) {
final count = provider.brushRecords.where((r) =>
r.dateTime.year == day.year &&
r.dateTime.month == day.month &&
r.dateTime.day == day.day
).length;
return List.generate(count, (i) => i);
},
eventLoader为每个日期返回事件列表。
这里统计当天的刷牙记录数量,返回对应长度的列表作为事件标记的依据。
calendarBuilders: CalendarBuilders(
markerBuilder: (context, date, events) {
if (events.isNotEmpty) {
return Positioned(
bottom: 1,
calendarBuilders允许自定义日历各部分的渲染方式。
markerBuilder用于自定义事件标记的显示,Positioned定位标记在日期下方。
child: Container(
width: 6,
height: 6,
decoration: const BoxDecoration(
color: Color(0xFF26A69A),
shape: BoxShape.circle,
),
),
);
}
return null;
},
),
),
);
}
有记录的日期下方显示一个6像素的小绿点。
简洁明了,不会干扰日期数字的显示。
记录分类网格布局
六种护理记录分类用网格布局展示,方便用户快速进入对应的记录页面。
Widget _buildRecordCategories(BuildContext context) {
final categories = [
{'icon': Icons.brush, 'label': '刷牙记录', 'color': const Color(0xFF26A69A), 'page': const BrushHistoryPage()},
用Map数组定义分类配置。
每个分类包含图标、文字标签、主题颜色和目标页面四个属性。
{'icon': Icons.water_drop, 'label': '漱口记录', 'color': const Color(0xFF42A5F5), 'page': const MouthwashPage()},
{'icon': Icons.linear_scale, 'label': '牙线记录', 'color': const Color(0xFFAB47BC), 'page': const FlossPage()},
漱口记录用蓝色,牙线记录用紫色。
不同分类使用不同颜色,视觉上更容易区分。
{'icon': Icons.restaurant, 'label': '饮食记录', 'color': const Color(0xFFFF7043), 'page': const DietRecordPage()},
{'icon': Icons.medical_services, 'label': '检查记录', 'color': const Color(0xFF5C6BC0), 'page': const OralCheckPage()},
{'icon': Icons.warning_amber, 'label': '口腔问题', 'color': const Color(0xFFEF5350), 'page': const OralIssuePage()},
];
饮食记录用橙色,检查记录用靛蓝色,口腔问题用红色表示警示。
颜色的选择既要美观,也要符合功能的语义含义。
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
GridView.builder创建网格布局。
shrinkWrap让高度自适应内容,NeverScrollableScrollPhysics禁用自身滚动避免冲突。
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
),
3列布局,childAspectRatio为1表示正方形格子。
横向和纵向间距都是12像素,格子之间保持适当距离。
itemCount: categories.length,
itemBuilder: (context, index) {
final category = categories[index];
return GestureDetector(
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => category['page'] as Widget)),
itemBuilder根据索引构建每个格子。
点击时使用Navigator.push跳转到对应的详情页面。
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [BoxShadow(color: Colors.grey.shade200, blurRadius: 5)],
),
白色背景卡片,12像素圆角,浅色阴影。
这种设计和日历卡片风格统一,整体视觉协调。
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: (category['color'] as Color).withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(category['icon'] as IconData, color: category['color'] as Color, size: 28),
),
图标用浅色圆形背景包裹,颜色是分类主色的10%透明度。
这种设计让图标更加突出,同时保持整体的轻盈感。
const SizedBox(height: 8),
Text(category['label'] as String, style: const TextStyle(fontSize: 13)),
],
),
),
);
},
);
}
图标下方显示分类名称,13像素字体大小适中。
Column居中对齐让内容在格子中央显示。
今日记录列表实现
展示当天的所有护理记录,让用户快速回顾今天的护理情况。
Widget _buildTodayRecords(AppProvider provider) {
final today = DateTime.now();
获取当前日期时间,用于筛选今天的记录。
final todayBrush = provider.brushRecords.where((r) =>
r.dateTime.year == today.year &&
r.dateTime.month == today.month &&
r.dateTime.day == today.day
).toList();
筛选今天的刷牙记录。
where方法配合年月日三个条件比较,精确筛选出当天的数据。
final todayMouthwash = provider.mouthwashRecords.where((r) =>
r.dateTime.year == today.year &&
r.dateTime.month == today.month &&
r.dateTime.day == today.day
).toList();
同样的方式筛选今天的漱口记录。
toList()把筛选结果转成List,方便后续遍历。
final todayFloss = provider.flossRecords.where((r) =>
r.dateTime.year == today.year &&
r.dateTime.month == today.month &&
r.dateTime.day == today.day
).toList();
筛选今天的牙线使用记录。
三种记录分别筛选,然后合并显示。
if (todayBrush.isEmpty && todayMouthwash.isEmpty && todayFloss.isEmpty) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text('今日暂无记录,快去护理吧!', style: TextStyle(color: Colors.grey)),
),
);
}
如果今天没有任何记录,显示友好的空状态提示。
鼓励用户去进行口腔护理,而不是冷冰冰的"暂无数据"。
return Column(
children: [
...todayBrush.map((record) => _buildRecordItem(
icon: Icons.brush,
color: const Color(0xFF26A69A),
title: '刷牙',
subtitle: '${record.durationSeconds ~/ 60}分${record.durationSeconds % 60}秒 · 评分${record.score}',
time: DateFormat('HH:mm').format(record.dateTime),
)),
展开运算符…把map结果展开成多个Widget。
刷牙记录显示时长和评分,时间用DateFormat格式化成"HH:mm"格式。
...todayMouthwash.map((record) => _buildRecordItem(
icon: Icons.water_drop,
color: const Color(0xFF42A5F5),
title: '漱口',
subtitle: '${record.productName} · ${record.durationSeconds}秒',
time: DateFormat('HH:mm').format(record.dateTime),
)),
漱口记录显示使用的产品名称和漱口时长。
蓝色图标和刷牙的绿色区分开来。
...todayFloss.map((record) => _buildRecordItem(
icon: Icons.linear_scale,
color: const Color(0xFFAB47BC),
title: '牙线',
subtitle: record.type,
time: DateFormat('HH:mm').format(record.dateTime),
)),
],
);
}
牙线记录显示使用的牙线类型。
紫色图标让三种记录在视觉上容易区分。
通用记录项组件
抽取通用的记录项组件,避免重复代码,提高可维护性。
Widget _buildRecordItem({
required IconData icon,
required Color color,
required String title,
required String subtitle,
required String time,
}) {
使用命名参数,required关键字确保调用时必须传入这些参数。
这种写法让代码更清晰,调用时也更容易理解每个参数的含义。
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
每条记录是一个白色圆角卡片。
底部有8像素间距,让多条记录之间有适当的分隔。
child: Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(icon, color: color, size: 24),
),
左侧是带背景的图标,颜色根据记录类型变化。
圆形浅色背景让图标更加醒目。
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
Text(subtitle, style: TextStyle(color: Colors.grey.shade600, fontSize: 13)),
],
),
),
中间是记录的标题和详情。
Expanded让文字区域占据剩余空间,标题加粗,详情用灰色小字。
Text(time, style: TextStyle(color: Colors.grey.shade500, fontSize: 13)),
],
),
);
}
}
右侧显示记录的时间,灰色小字不抢眼但信息完整。
整个布局使用Row横向排列,结构清晰。
小结
记录页面通过日历和列表的组合,为用户提供了完整的护理记录查看体验。日历组件支持周视图和月视图切换,有记录的日期会显示小绿点标记,让用户一眼看出哪些天坚持了护理。分类网格让用户快速进入各类记录的详情页面,今日记录列表则展示当天的护理情况。整个页面的设计注重信息的层次和可读性,配色方案与首页保持一致,给用户统一的视觉体验。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)