Flutter for OpenHarmony 家具购买记录App实战:添加房间实现
本文介绍了添加房间页面的设计与实现,重点包括: 页面功能设计 包含预览区、名称输入、图标/颜色选择器、备注和保存按钮 预览区实时反馈选择效果 主要组件实现 使用StatefulWidget管理选中状态 图标选择器采用Wrap布局实现多行显示 颜色选择器展示6种预设颜色 预览组件同步显示当前选择效果 交互特点 提供沙发、床等8种房间图标 保持与列表页一致的6种颜色方案 选中项高亮显示 该页面通过可视
上一篇讲了房间列表页面,这篇来讲添加房间页面。这个页面比较有意思,除了普通的文本输入,还有图标选择器和颜色选择器,用户可以自定义房间的图标和颜色。
添加房间页面的设计目标是让用户能快速创建一个有辨识度的房间。通过选择不同的图标和颜色,每个房间在列表里都能一眼认出来。
页面设计思路
添加房间页面包含以下几个部分:
- 预览区域:实时显示选择的图标和颜色效果
- 房间名称输入框
- 图标选择器:提供多个图标供选择
- 颜色选择器:提供多个颜色供选择
- 备注输入框
- 保存按钮
预览区域是这个页面的亮点,用户选择图标或颜色后,能立即看到效果,不用等到保存后才知道长什么样。
页面基础结构
添加房间页面用 StatefulWidget,因为要管理选中的图标和颜色状态:
class AddRoomPage extends StatefulWidget {
const AddRoomPage({super.key});
State<AddRoomPage> createState() => _AddRoomPageState();
}
状态类里定义选中的图标、颜色,以及可选项列表:
class _AddRoomPageState extends State<AddRoomPage> {
IconData _selectedIcon = Icons.weekend;
Color _selectedColor = const Color(0xFF8B4513);
final _icons = [
Icons.weekend, Icons.bed, Icons.single_bed, Icons.menu_book,
Icons.restaurant, Icons.kitchen, Icons.bathtub, Icons.balcony
];
final _colors = [
const Color(0xFF8B4513), const Color(0xFF6B8E23),
const Color(0xFF4682B4), const Color(0xFF9370DB),
const Color(0xFFCD853F), const Color(0xFF20B2AA)
];
_selectedIcon 和 _selectedColor 是当前选中的图标和颜色,默认是客厅的图标和棕色。
图标列表包含了常见房间类型的图标:沙发(客厅)、床(卧室)、单人床(次卧)、书本(书房)、餐具(餐厅)、厨房、浴缸(卫生间)、阳台。
颜色列表和房间列表页面用的颜色一致,保持视觉统一。
build 方法实现
build 方法构建整个页面:
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFFAF8F5),
appBar: AppBar(
title: const Text('添加房间'),
backgroundColor: const Color(0xFF8B4513),
foregroundColor: Colors.white
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
children: [
_buildPreview(),
SizedBox(height: 20.h),
_buildSection('房间信息', [
_buildField('房间名称', '请输入房间名称', Icons.room)
]),
页面最上面是预览区域,然后是房间名称输入框。预览区域和下面的内容之间留了 20 的间距,比普通区块间距大一些,突出预览的重要性。
图标和颜色选择区块
继续看后面的区块:
SizedBox(height: 16.h),
_buildSection('选择图标', [_buildIconSelector()]),
SizedBox(height: 16.h),
_buildSection('选择颜色', [_buildColorSelector()]),
SizedBox(height: 16.h),
_buildSection('备注', [_buildNotesField()]),
SizedBox(height: 24.h),
_buildSaveButton(),
],
),
),
);
}
图标选择和颜色选择各占一个区块,备注是可选的,用户可以填一些额外信息。
预览组件
预览组件实时显示选择的效果:
Widget _buildPreview() {
return Container(
padding: EdgeInsets.all(24.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16.r)
),
child: Column(
children: [
Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
color: _selectedColor.withOpacity(0.1),
shape: BoxShape.circle
),
child: Icon(_selectedIcon, color: _selectedColor, size: 40.sp),
),
SizedBox(height: 12.h),
Text('预览效果', style: TextStyle(color: Colors.grey[600], fontSize: 12.sp)),
],
),
);
}
预览区域是一个白色卡片,中间显示一个圆形图标容器,和房间列表页面的卡片样式一致。图标和背景色都用 _selectedColor,用户选择颜色后会立即更新。
下面有个"预览效果"的提示文字,让用户知道这是预览而不是最终结果。
区块容器组件
_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,
]
),
);
}
这个方法在多个页面都用到了,可以考虑抽成公共组件。
文本输入框组件
房间名称输入框:
Widget _buildField(String label, String hint, IconData icon) {
return TextFormField(
decoration: InputDecoration(
labelText: label,
hintText: hint,
prefixIcon: Icon(icon, color: const Color(0xFF8B4513)),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10.r)),
),
);
}
和其他页面的输入框样式一致,左边有个房间图标。
图标选择器
图标选择器用 Wrap 布局,让图标自动换行:
Widget _buildIconSelector() {
return Wrap(
spacing: 12.w,
runSpacing: 12.h,
children: _icons.map((icon) => GestureDetector(
onTap: () => setState(() => _selectedIcon = icon),
child: Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: _selectedIcon == icon
? _selectedColor.withOpacity(0.2)
: Colors.grey[100],
borderRadius: BorderRadius.circular(10.r),
border: _selectedIcon == icon
? Border.all(color: _selectedColor, width: 2)
: null,
),
child: Icon(
icon,
color: _selectedIcon == icon ? _selectedColor : Colors.grey[600],
size: 24.sp
),
),
)).toList(),
);
}
Wrap 组件会自动换行,spacing 是水平间距,runSpacing 是行间距。
每个图标是一个可点击的容器,点击后更新 _selectedIcon 状态。选中的图标有三个视觉变化:
- 背景色变成选中颜色的浅色版本
- 加上选中颜色的边框
- 图标颜色变成选中颜色
未选中的图标背景是浅灰色,图标是深灰色,视觉上比较低调。
颜色选择器
颜色选择器也用 Wrap 布局:
Widget _buildColorSelector() {
return Wrap(
spacing: 12.w,
runSpacing: 12.h,
children: _colors.map((color) => GestureDetector(
onTap: () => setState(() => _selectedColor = color),
child: Container(
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: _selectedColor == color
? Border.all(color: Colors.white, width: 3)
: null,
boxShadow: _selectedColor == color
? [BoxShadow(color: color.withOpacity(0.5), blurRadius: 8)]
: null,
),
child: _selectedColor == color
? const Icon(Icons.check, color: Colors.white, size: 20)
: null,
),
)).toList(),
);
}
每个颜色是一个圆形色块,点击后更新 _selectedColor 状态。选中的颜色有三个视觉变化:
- 加上白色边框
- 加上同色系的阴影,有发光效果
- 中间显示白色对勾图标
这些效果组合起来,选中状态非常明显,用户一眼就能看出选的是哪个颜色。
备注输入框
备注是多行文本输入框:
Widget _buildNotesField() {
return TextFormField(
maxLines: 3,
decoration: InputDecoration(
hintText: '请输入备注信息...',
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10.r))
),
);
}
maxLines: 3 让输入框显示 3 行高度,适合输入较长的备注内容。没有 label,只有 hint 提示文字,因为区块标题已经说明了这是备注。
保存按钮
保存按钮和其他页面一样:
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)),
),
);
}
点击保存后返回上一页,显示成功提示。实际项目中要先验证房间名称不为空,然后保存到数据库。
选择器的交互设计
图标选择器和颜色选择器的交互设计有几个要点:
第一,即时反馈。点击后立即更新状态,预览区域也立即更新,用户能马上看到效果。
第二,选中状态明显。通过背景色、边框、阴影、图标等多种方式强调选中状态,不会让用户搞不清选的是哪个。
第三,未选中状态低调。未选中的选项用灰色系,不会干扰用户的注意力。
第四,点击区域足够大。每个选项都有足够的 padding,手指点击不会点错。
Wrap 布局的优势
用 Wrap 而不是 Row 或 GridView 的原因:
Row 不会自动换行,如果选项太多会溢出。GridView 需要指定固定的列数,不够灵活。
Wrap 会根据可用宽度自动换行,选项多了就多显示几行,选项少了就少显示几行。而且不需要指定高度,内容有多少就显示多少。
对于这种选项数量不固定的场景,Wrap 是最合适的选择。
状态联动
这个页面有个有趣的状态联动:选择颜色后,图标选择器里选中图标的颜色也会变。
因为图标选择器里用的是 _selectedColor:
color: _selectedIcon == icon ? _selectedColor : Colors.grey[600]
所以当 _selectedColor 变化时,选中图标的颜色也会跟着变。这种联动让整个页面的视觉效果更统一。
扩展性考虑
如果以后要支持用户自定义图标或颜色,可以这样扩展:
图标方面,可以加一个"更多图标"按钮,点击后弹出完整的图标选择器,让用户从 Material Icons 里选择任意图标。
颜色方面,可以加一个"自定义颜色"按钮,点击后弹出颜色选择器,让用户选择任意颜色。Flutter 有 flutter_colorpicker 这样的库可以用。
不过对于房间管理这个场景,预设的选项应该够用了,不需要太复杂。
小结
添加房间页面的核心是图标选择器和颜色选择器的实现。通过 Wrap 布局让选项自动换行,通过状态管理实现选中效果和预览联动。
预览区域是这个页面的亮点,让用户在保存前就能看到最终效果,提升了用户体验。
下一篇会讲房间详情页面的实现,展示房间里的家具列表,以及一些统计信息。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)