Flutter for OpenHarmony 家具购买记录App实战:添加家具实现
做完家具列表和详情页之后,接下来要实现添加家具的功能。这个页面是整个App的核心功能之一,用户需要在这里录入新购买的家具信息,包括基本信息、购买信息、保修信息和规格信息等。说实话,表单页面是我觉得最考验耐心的页面类型。字段多、验证逻辑复杂、用户体验要求高。但做好了,用户用起来会很顺手,所以还是值得花时间打磨的。
做完家具列表和详情页之后,接下来要实现添加家具的功能。这个页面是整个App的核心功能之一,用户需要在这里录入新购买的家具信息,包括基本信息、购买信息、保修信息和规格信息等。
说实话,表单页面是我觉得最考验耐心的页面类型。字段多、验证逻辑复杂、用户体验要求高。但做好了,用户用起来会很顺手,所以还是值得花时间打磨的。
页面整体设计思路
添加家具页面采用分组表单的设计,把相关的字段放在一起,用卡片区分不同的信息类别。这样用户填写的时候思路更清晰,不会被一大堆输入框搞晕。
页面使用 StatefulWidget,因为有下拉选择框需要管理状态。整体布局用 SingleChildScrollView 包裹,表单内容多的时候可以滚动。
页面基础结构
先看页面的基础结构,包括状态变量和 build 方法的整体框架:
class AddFurniturePage extends StatefulWidget {
const AddFurniturePage({super.key});
State<AddFurniturePage> createState() => _AddFurniturePageState();
}
这里用 StatefulWidget 是因为页面里有两个下拉选择框,选中的值需要用状态来管理。如果用 StatelessWidget,下拉框选择后没法更新显示。
接下来是状态类的定义:
class _AddFurniturePageState extends State<AddFurniturePage> {
String _room = '客厅';
String _category = '沙发';
final _rooms = ['客厅', '卧室', '书房', '餐厅', '厨房', '卫生间', '阳台'];
final _categories = ['沙发', '床', '桌子', '椅子', '柜子', '灯具', '家电', '其他'];
_room 和 _category 是两个下拉框的当前选中值,默认分别是"客厅"和"沙发"。_rooms 和 _categories 是下拉选项列表,用 final 修饰因为它们不会变。
为什么要预设这些选项而不是让用户自己输入?主要是为了数据的一致性。如果用户随便输入,可能会出现"客厅"、“客廳”、"living room"这种同一个意思但写法不同的情况,后面做统计分析就麻烦了。
build 方法实现
build 方法里构建整个页面的 UI 结构:
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFFAF8F5),
appBar: AppBar(
title: const Text('添加家具'),
backgroundColor: const Color(0xFF8B4513),
foregroundColor: Colors.white
),
背景色用米白色 0xFFFAF8F5,和整个 App 保持一致。AppBar 用棕色主题色,标题文字白色。这个配色方案贯穿整个 App,保持视觉统一。
页面主体用 SingleChildScrollView 包裹,内容多的时候可以滚动:
body: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
children: [
_buildSection('基本信息', [
_buildField('家具名称', '请输入家具名称', Icons.chair),
_buildDropdown('所属房间', _rooms, _room, (v) => setState(() => _room = v!)),
_buildDropdown('家具分类', _categories, _category, (v) => setState(() => _category = v!)),
_buildField('品牌', '请输入品牌名称', Icons.business),
]),
表单分成四个区块:基本信息、购买信息、保修信息、规格信息。每个区块用 _buildSection 方法构建,传入标题和字段列表。
这种写法的好处是结构清晰,每个区块的字段一目了然。如果以后要加减字段,直接在数组里改就行了。
购买信息和保修信息区块
继续看后面几个区块的代码:
SizedBox(height: 16.h),
_buildSection('购买信息', [
_buildField('购买价格', '请输入价格', Icons.payment),
_buildDateField('购买日期'),
_buildField('购买商家', '请输入商家名称', Icons.store),
]),
SizedBox(height: 16.h),
_buildSection('保修信息', [
_buildDateField('保修到期日期'),
_buildField('保修电话', '请输入保修联系电话', Icons.phone),
]),
区块之间用 SizedBox(height: 16.h) 隔开,16 是个比较舒适的间距,不会太挤也不会太散。
购买信息包括价格、日期、商家三个字段。保修信息包括到期日期和联系电话。这些都是买家具时比较重要的信息,以后查保修的时候会用到。
规格信息和保存按钮
最后是规格信息区块和保存按钮:
SizedBox(height: 16.h),
_buildSection('规格信息', [
_buildField('尺寸', '长×宽×高 (cm)', Icons.straighten),
_buildField('颜色', '请输入颜色', Icons.palette),
_buildField('材质', '请输入材质', Icons.texture),
]),
SizedBox(height: 24.h),
_buildSaveButton(),
],
),
),
);
}
规格信息包括尺寸、颜色、材质。这些信息对于家具来说挺重要的,比如买窗帘的时候需要知道窗户尺寸,买沙发套需要知道沙发尺寸。
保存按钮放在最下面,和最后一个区块之间留了 24 的间距,比区块之间的间距大一些,视觉上有个收尾的感觉。
区块容器组件
_buildSection 方法用来构建每个表单区块的容器:
Widget _buildSection(String title, List<Widget> children) {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16.r)
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: const Color(0xFF5D4037)
)),
SizedBox(height: 16.h),
...children,
]
),
);
}
每个区块是一个白色圆角卡片,内边距 16。标题用深棕色 0xFF5D4037,字号 16,加粗显示。标题和内容之间留 16 的间距。
...children 是 Dart 的展开运算符,把传入的 Widget 列表展开放到 Column 里。这样调用的时候可以直接传数组,不用手动展开。
文本输入框组件
_buildField 方法构建普通的文本输入框:
Widget _buildField(String label, String hint, IconData icon) {
return Padding(
padding: EdgeInsets.only(bottom: 12.h),
child: TextFormField(
decoration: InputDecoration(
labelText: label,
hintText: hint,
prefixIcon: Icon(icon, color: const Color(0xFF8B4513)),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10.r)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.r),
borderSide: const BorderSide(color: Color(0xFF8B4513))
),
),
),
);
}
每个输入框底部留 12 的间距,这样多个输入框叠在一起不会太挤。
prefixIcon 是输入框左边的图标,用主题棕色。图标能帮助用户快速识别这个字段是干什么的,比如看到椅子图标就知道是家具名称,看到钱的图标就知道是价格。
focusedBorder 设置输入框获得焦点时的边框颜色,用主题棕色,和整体风格统一。默认的蓝色焦点边框和棕色主题不太搭。
下拉选择框组件
_buildDropdown 方法构建下拉选择框:
Widget _buildDropdown(String label, List<String> items, String value, Function(String?) onChanged) {
return Padding(
padding: EdgeInsets.only(bottom: 12.h),
child: DropdownButtonFormField<String>(
value: value,
decoration: InputDecoration(
labelText: label,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10.r))
),
items: items.map((i) => DropdownMenuItem(value: i, child: Text(i))).toList(),
onChanged: onChanged,
),
);
}
DropdownButtonFormField 是 Flutter 提供的表单下拉框组件,和 TextFormField 风格一致,放在一起比较协调。
items 参数通过 map 把字符串列表转换成 DropdownMenuItem 列表。onChanged 回调在选择改变时触发,外部传入的是 setState 调用,用来更新状态。
为什么用 DropdownButtonFormField 而不是普通的 DropdownButton?因为前者自带表单样式,有 label、边框这些,和其他输入框放在一起更统一。
日期选择器组件
_buildDateField 方法构建日期选择输入框:
Widget _buildDateField(String label) {
return Padding(
padding: EdgeInsets.only(bottom: 12.h),
child: TextFormField(
readOnly: true,
decoration: InputDecoration(
labelText: label,
prefixIcon: const Icon(Icons.calendar_today, color: Color(0xFF8B4513)),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10.r)),
),
onTap: () async {
await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2030)
);
},
),
);
}
日期输入框设置 readOnly: true,用户不能直接输入文字,只能通过点击弹出日期选择器来选择日期。这样可以保证日期格式的一致性。
showDatePicker 是 Flutter 内置的日期选择器,initialDate 设为当前日期,firstDate 和 lastDate 限制可选范围。这里设置从 2000 年到 2030 年,基本覆盖了家具购买的合理时间范围。
日期图标用日历图标 Icons.calendar_today,用户一看就知道这是选日期的。
保存按钮组件
_buildSaveButton 方法构建底部的保存按钮:
Widget _buildSaveButton() {
return SizedBox(
width: double.infinity,
height: 50.h,
child: ElevatedButton(
onPressed: () {
Get.back();
Get.snackbar('成功', '家具添加成功',
backgroundColor: Colors.green,
colorText: Colors.white
);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF8B4513),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r))
),
child: Text('保存', style: TextStyle(fontSize: 16.sp, color: Colors.white)),
),
);
}
按钮宽度设为 double.infinity 撑满整行,高度 50。用主题棕色作为背景色,圆角 12。
点击保存后,先用 Get.back() 返回上一页,然后用 Get.snackbar 显示成功提示。Snackbar 用绿色背景表示成功,这是比较通用的颜色约定。
实际项目中,这里应该先做表单验证,验证通过后再保存数据到数据库。现在先把 UI 做出来,数据层后面再接入。
表单验证的考虑
虽然现在没有实现表单验证,但设计的时候已经考虑到了。TextFormField 支持 validator 参数,可以传入验证函数:
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入家具名称';
}
return null;
},
)
验证失败时返回错误信息,验证通过返回 null。配合 Form 组件和 GlobalKey<FormState>,可以在保存时统一验证所有字段。
这个功能后面接入数据层的时候再加,现在先专注于 UI 实现。
屏幕适配说明
整个页面大量使用了 flutter_screenutil 的适配方法:
.w用于宽度相关的值,比如 padding、margin.h用于高度相关的值,比如 SizedBox 的 height.sp用于字号.r用于圆角
这样在不同尺寸的设备上,UI 都能保持合适的比例。比如在平板上,按钮不会显得太小,在小屏手机上也不会太大。
用户体验优化点
这个页面在用户体验上做了几个优化:
第一,表单分组。把相关的字段放在一起,用户填写时思路更清晰。比如购买信息放一组,保修信息放一组。
第二,输入框带图标。图标能帮助用户快速识别字段用途,减少阅读 label 的时间。
第三,下拉选择代替手动输入。对于房间、分类这种有限选项的字段,用下拉框可以减少输入错误,也能保证数据一致性。
第四,日期选择器。日期用选择器而不是手动输入,避免格式错误,用户操作也更方便。
小结
添加家具页面是一个典型的表单页面,核心是把字段组织好,让用户填写起来顺畅。通过分组、图标、下拉框、日期选择器这些设计,可以大大提升用户体验。
代码组织上,把每种输入组件抽成独立方法,主 build 方法就很清晰。以后要修改某个组件的样式,直接找到对应方法改就行了。
下一篇会讲编辑家具页面的实现,和添加页面类似但有一些不同的处理,比如需要回显已有数据、支持删除操作等。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)