在健康管理类应用中,体重记录是最基础也是最常用的功能之一。作为一名长期跟踪健康数据开发的工程师,我发现很多用户都有定期记录体重的习惯,但市面上不少应用的体重记录体验并不理想——要么输入方式繁琐,要么界面反馈不够直观。
这次我们要为OpenHarmony平台开发一个跨平台的健康记录应用,其中体重记录模块采用了Flutter框架实现。选择Flutter主要基于三点考虑:首先是跨平台一致性,一套代码可以同时运行在HarmonyOS和Android/iOS设备上;其次是热重载带来的开发效率提升;最后是Flutter丰富的动画支持能力,这对打造流畅的滑块交互至关重要。
在体重输入方式上,我们对比了三种常见方案:
最终选择滑块方案基于以下实测数据:
体重记录页面需要维护两个核心状态:
dart复制class _AddWeightPageState extends State<AddWeightPage> {
double _weight = 65.5; // 当前体重值
final TextEditingController _noteController = TextEditingController(); // 备注控制器
}
这里有几个设计细节值得注意:
整个页面采用典型的Material Design表单布局,但做了几点优化:
dart复制return Scaffold(
backgroundColor: const Color(0xFFFAFAFC), // 浅灰色背景
appBar: AppBar(
backgroundColor: Colors.transparent, // 透明导航栏
leading: IconButton(icon: Icon(Icons.close_rounded), ...), // 关闭按钮
actions: [
TextButton(onPressed: _saveRecord, child: Text('保存')) // 主题色保存按钮
],
),
body: SingleChildScrollView(
child: Column(
children: [
_buildWeightPicker(), // 体重选择器
_buildTimeSelector(), // 时间选择
_buildNoteInput(), // 备注输入
],
),
),
);
关键技巧:使用SingleChildScrollView包裹内容区域,配合EdgeInsets.all(20.w)的padding,可以完美解决键盘弹出时的布局挤压问题。
Flutter原生的Slider组件需要深度定制才能达到理想的交互效果:
dart复制SliderTheme(
data: SliderThemeData(
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))),
),
)
这里有几个工程实践要点:
toStringAsFixed(1)确保数值精度为0.1kg体重数字的显示需要特别处理视觉平衡:
dart复制Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(_weight.toStringAsFixed(1),
style: TextStyle(fontSize: 56.sp, fontWeight: FontWeight.w700)),
Padding(
padding: EdgeInsets.only(bottom: 10.h),
child: Text('kg', style: TextStyle(fontSize: 20.sp, color: Colors.grey[500])),
),
],
)
这种布局方式实现了:
对于健康数据这类敏感信息,我们采用本地存储为主的设计:
dart复制void _saveRecord() async {
final record = {
'value': _weight,
'time': DateTime.now().toIso8601String(),
'note': _noteController.text,
};
try {
final prefs = await SharedPreferences.getInstance();
final records = prefs.getStringList('weight_records') ?? [];
records.add(jsonEncode(record));
await prefs.setStringList('weight_records', records);
Get.back();
Get.snackbar('成功', '体重记录已保存');
} catch (e) {
Get.snackbar('错误', '保存失败: ${e.toString()}');
}
}
重要提示:实际项目中应该使用加密存储(如flutter_secure_storage)来保护健康数据,这里简化处理仅作演示。
完善的输入验证是健康类应用的必备功能:
dart复制bool _validateInput() {
if (_weight < 30 || _weight > 150) {
Get.snackbar('数据异常', '请输入30-150kg之间的合理体重值');
return false;
}
if (_noteController.text.length > 100) {
Get.snackbar('备注过长', '备注内容请控制在100字以内');
return false;
}
return true;
}
验证逻辑应该包括:
将大Widget拆分为多个构建方法可以显著提升性能:
dart复制@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
_buildWeightPicker(), // 独立构建方法
_buildTimeSelector(),
_buildNoteInput(),
],
),
);
}
这种做法的优势:
将样式常量提取到类顶部可以减少重复构建:
dart复制class _AddWeightPageState extends State<AddWeightPage> {
static const _textStyle = TextStyle(fontSize: 56.sp, fontWeight: FontWeight.w700);
static const _unitStyle = TextStyle(fontSize: 20.sp, color: Colors.grey);
Widget _buildWeightDisplay() {
return Text(_weight.toStringAsFixed(1), style: _textStyle);
}
}
在低端设备上可能出现滑块不跟手的情况,解决方案:
toStringAsFixed(1)限制精度当备注输入框获取焦点时,键盘可能遮挡内容。完整解决方案:
dart复制body: KeyboardDismissOnTap(
child: SingleChildScrollView(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom + 20.h
),
child: Column(...),
),
)
其中KeyboardDismissOnTap是自定义组件,用于点击空白处收起键盘。
可以根据BMI给出健康建议:
dart复制String _getHealthStatus(double weight, double height) {
final bmi = weight / (height * height);
if (bmi < 18.5) return '偏瘦';
if (bmi < 24) return '正常';
if (bmi < 28) return '超重';
return '肥胖';
}
添加体重变化曲线图:
dart复制void _showTrendChart() {
Get.to(() => LineChart(
data: _weightRecords.map((r) => r['value']).toList(),
));
}
实现这个功能需要集成charts_flutter等图表库。
在真实项目开发中,我发现体重记录模块虽然看似简单,但要打磨出优秀的用户体验需要关注大量细节。比如滑块阻尼效果的调试就花费了我们整整两天时间,最终找到了最适合体重调整的物理模型参数。这种对细节的执着是一个合格Flutter开发者的必备素质。