Flutter for OpenHarmony 身体健康状况记录App实战 - 添加体重记录实现
本文介绍了如何实现一个体重记录页面,包含以下核心功能: 页面布局采用经典表单结构,顶部导航栏带关闭按钮和保存功能 体重选择器为核心组件,超大数字显示当前体重(保留1位小数) 滑块组件实现直观的数值调整,支持30-150kg范围 自动显示体重状态标签(正常/偏高/偏低) 底部包含时间选择和备注输入功能 页面采用紫色主题色,通过SliderTheme自定义滑块样式,优化了交互体验。保存功能会显示成功提
前言
体重是最常见的健康指标之一,很多人每天都会称体重。这个页面需要提供一个直观的输入方式,让用户快速记录当天的体重数据。我们会用滑块组件来选择体重值,比手动输入数字更方便。
这篇文章会实现一个完整的体重记录页面,包括数值选择、时间显示、备注输入等功能。
页面状态定义
体重记录页面需要维护两个状态:当前选择的体重值和备注内容。
class AddWeightPage extends StatefulWidget {
const AddWeightPage({super.key});
State<AddWeightPage> createState() => _AddWeightPageState();
}
class _AddWeightPageState extends State<AddWeightPage> {
double _weight = 65.5;
final TextEditingController _noteController = TextEditingController();
_weight 初始值设为 65.5,这是一个比较常见的体重数值。实际项目中可以读取用户上次记录的体重作为默认值。
_noteController 用来控制备注输入框,虽然这个页面里没有用到它的高级功能,但保留 Controller 方便后续扩展,比如获取输入内容、清空输入等。
页面整体布局
页面采用经典的表单布局:顶部导航栏、中间内容区、底部留白。
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFFAFAFC),
appBar: AppBar(
backgroundColor: Colors.transparent,
leading: IconButton(
icon: Icon(Icons.close_rounded, size: 24.w),
onPressed: () => Get.back(),
),
title: Text('记录体重', style: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.w600
)),
centerTitle: true,
actions: [
TextButton(
onPressed: () {
Get.back();
Get.snackbar('成功', '体重记录已保存', backgroundColor: Colors.white);
},
child: Text('保存', style: TextStyle(
fontSize: 15.sp,
color: const Color(0xFF6C63FF),
fontWeight: FontWeight.w600
)),
),
],
),
body: SingleChildScrollView(
padding: EdgeInsets.all(20.w),
child: Column(
children: [
_buildWeightPicker(),
SizedBox(height: 24.h),
_buildTimeSelector(),
SizedBox(height: 16.h),
_buildNoteInput(),
],
),
),
);
}
导航栏左边用关闭图标而不是返回箭头,这是添加/编辑类页面的常见设计,暗示用户可以"取消"这个操作。
右边的保存按钮用主题色文字,点击后先关闭页面,再显示一个 Snackbar 提示保存成功。实际项目中这里应该先调用保存接口,成功后再关闭页面。
SingleChildScrollView 包裹内容区域,防止键盘弹出时内容被遮挡。
体重选择器
这是页面的核心组件,用一个大号数字显示当前体重,下面是滑块用来调整数值。
Widget _buildWeightPicker() {
return Container(
padding: EdgeInsets.symmetric(vertical: 40.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24.r),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(_weight.toStringAsFixed(1), style: TextStyle(
fontSize: 56.sp,
fontWeight: FontWeight.w700,
color: const Color(0xFF1A1A2E)
)),
Padding(
padding: EdgeInsets.only(bottom: 10.h),
child: Text(' kg', style: TextStyle(
fontSize: 20.sp,
color: Colors.grey[500]
)),
),
],
),
体重数值用 56sp 的超大字号,是整个页面的视觉焦点。toStringAsFixed(1) 保留一位小数,比如显示 “65.5” 而不是 “65.50000”。
单位 “kg” 用较小的字号,通过 crossAxisAlignment: CrossAxisAlignment.end 让它和数字底部对齐,再用 padding 微调位置。
状态标签
数值下方显示一个状态标签,告诉用户当前体重是否在正常范围内。
SizedBox(height: 8.h),
Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h),
decoration: BoxDecoration(
color: const Color(0xFF00C9A7).withOpacity(0.12),
borderRadius: BorderRadius.circular(12.r),
),
child: Text('正常范围', style: TextStyle(
fontSize: 12.sp,
color: const Color(0xFF00C9A7)
)),
),
绿色 #00C9A7 表示正常状态。实际项目中应该根据用户的身高计算 BMI,然后判断体重是否在健康范围内。如果偏高或偏低,可以换成黄色或红色。
滑块组件
滑块是调整体重的主要交互方式,比手动输入更直观。
SizedBox(height: 30.h),
Padding(
padding: EdgeInsets.symmetric(horizontal: 30.w),
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
activeTrackColor: const Color(0xFF6C63FF),
inactiveTrackColor: Colors.grey[200],
thumbColor: const Color(0xFF6C63FF),
overlayColor: const Color(0xFF6C63FF).withOpacity(0.2),
trackHeight: 6.h,
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 12.r),
),
child: Slider(
value: _weight,
min: 30,
max: 150,
onChanged: (v) => setState(() => _weight = double.parse(v.toStringAsFixed(1))),
),
),
),
SliderTheme 用来自定义滑块样式。默认的 Material 滑块颜色是蓝色,我们改成主题色紫色。
trackHeight: 6.h 让轨道粗一点,更容易点击。thumbShape 设置滑块圆点的大小,12r 的半径在手机上比较好操作。
onChanged 回调里用 toStringAsFixed(1) 再 parse 回来,是为了把数值四舍五入到一位小数。不然滑块会产生很多位小数,比如 65.4999999。
范围标签
滑块下方显示最小值和最大值,让用户知道可选范围。
Padding(
padding: EdgeInsets.symmetric(horizontal: 40.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('30 kg', style: TextStyle(fontSize: 12.sp, color: Colors.grey[400])),
Text('150 kg', style: TextStyle(fontSize: 12.sp, color: Colors.grey[400])),
],
),
),
],
),
);
}
30kg 到 150kg 的范围覆盖了绝大多数成年人的体重。如果是儿童版App,可能需要调整这个范围。
灰色小字不会抢夺注意力,但需要时能看到。
时间选择器
显示当前记录的时间,点击可以修改。
Widget _buildTimeSelector() {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16.r),
),
child: Row(
children: [
Icon(Icons.access_time_rounded, size: 22.w, color: Colors.grey[600]),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('记录时间', style: TextStyle(
fontSize: 12.sp,
color: Colors.grey[500]
)),
SizedBox(height: 2.h),
Text('今天 ${TimeOfDay.now().format(context)}', style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.w500,
color: const Color(0xFF1A1A2E)
)),
],
),
),
Icon(Icons.chevron_right_rounded, size: 20.w, color: Colors.grey[400]),
],
),
);
}
TimeOfDay.now().format(context) 会根据系统设置显示 12 小时制或 24 小时制的时间。
右边的箭头图标暗示这是一个可点击的区域。点击后应该弹出时间选择器,让用户修改记录时间。这个功能可以用 showTimePicker 实现。
备注输入
有时候用户想记录一些额外信息,比如"早餐前称的"、"运动后称的"等。
Widget _buildNoteInput() {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('备注', style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
color: const Color(0xFF1A1A2E)
)),
SizedBox(height: 10.h),
TextField(
controller: _noteController,
maxLines: 3,
decoration: InputDecoration(
hintText: '添加备注信息...',
hintStyle: TextStyle(fontSize: 14.sp, color: Colors.grey[400]),
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
),
),
],
),
);
}
}
maxLines: 3 让输入框显示三行高度,用户可以输入多行文字。
border: InputBorder.none 去掉输入框的边框,因为外层容器已经有圆角背景了,再加边框会显得多余。
contentPadding: EdgeInsets.zero 去掉输入框的内边距,让文字紧贴容器边缘。
数据保存逻辑
当前的保存按钮只是显示一个提示,实际项目中需要把数据存到本地数据库或上传到服务器。
onPressed: () {
// 实际项目中的保存逻辑
// final record = WeightRecord(
// value: _weight,
// time: DateTime.now(),
// note: _noteController.text,
// );
// await database.insert(record);
Get.back();
Get.snackbar('成功', '体重记录已保存', backgroundColor: Colors.white);
},
如果用 SQLite 存储,可以定义一个 WeightRecord 模型类,包含体重值、记录时间、备注等字段。保存时创建实例并插入数据库。
如果用云端存储,需要调用 API 接口,处理网络请求的 loading 状态和错误情况。
输入验证
虽然滑块限制了输入范围,但保存前最好还是做一下验证:
void _saveRecord() {
if (_weight < 30 || _weight > 150) {
Get.snackbar('错误', '体重数值不在有效范围内');
return;
}
// 保存逻辑...
}
这种防御性编程可以避免一些边界情况,比如用户通过某种方式绕过了滑块的限制。
小结
这个体重记录页面实现了以下功能:
- 滑块选择体重值,精确到 0.1kg
- 显示当前体重是否在正常范围
- 显示和修改记录时间
- 添加备注信息
- 保存数据并返回
滑块交互比数字键盘更适合这种有明确范围的数值输入。用户拖动滑块时能直观看到数值变化,体验更好。
下一篇会讲添加血压记录页面的实现,血压需要同时记录收缩压和舒张压两个数值,交互设计会有所不同。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)