请添加图片描述

前言

体重是最常见的健康指标之一,很多人每天都会称体重。这个页面需要提供一个直观的输入方式,让用户快速记录当天的体重数据。我们会用滑块组件来选择体重值,比手动输入数字更方便。

这篇文章会实现一个完整的体重记录页面,包括数值选择、时间显示、备注输入等功能。


页面状态定义

体重记录页面需要维护两个状态:当前选择的体重值和备注内容。

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐