请添加图片描述

上一篇讲了房间列表页面,这篇来讲添加房间页面。这个页面比较有意思,除了普通的文本输入,还有图标选择器和颜色选择器,用户可以自定义房间的图标和颜色。

添加房间页面的设计目标是让用户能快速创建一个有辨识度的房间。通过选择不同的图标和颜色,每个房间在列表里都能一眼认出来。

页面设计思路

添加房间页面包含以下几个部分:

  1. 预览区域:实时显示选择的图标和颜色效果
  2. 房间名称输入框
  3. 图标选择器:提供多个图标供选择
  4. 颜色选择器:提供多个颜色供选择
  5. 备注输入框
  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 状态。选中的图标有三个视觉变化:

  1. 背景色变成选中颜色的浅色版本
  2. 加上选中颜色的边框
  3. 图标颜色变成选中颜色

未选中的图标背景是浅灰色,图标是深灰色,视觉上比较低调。

颜色选择器

颜色选择器也用 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 状态。选中的颜色有三个视觉变化:

  1. 加上白色边框
  2. 加上同色系的阴影,有发光效果
  3. 中间显示白色对勾图标

这些效果组合起来,选中状态非常明显,用户一眼就能看出选的是哪个颜色。

备注输入框

备注是多行文本输入框:

  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 而不是 RowGridView 的原因:

Row 不会自动换行,如果选项太多会溢出。GridView 需要指定固定的列数,不够灵活。

Wrap 会根据可用宽度自动换行,选项多了就多显示几行,选项少了就少显示几行。而且不需要指定高度,内容有多少就显示多少。

对于这种选项数量不固定的场景,Wrap 是最合适的选择。

状态联动

这个页面有个有趣的状态联动:选择颜色后,图标选择器里选中图标的颜色也会变。

因为图标选择器里用的是 _selectedColor

color: _selectedIcon == icon ? _selectedColor : Colors.grey[600]

所以当 _selectedColor 变化时,选中图标的颜色也会跟着变。这种联动让整个页面的视觉效果更统一。

扩展性考虑

如果以后要支持用户自定义图标或颜色,可以这样扩展:

图标方面,可以加一个"更多图标"按钮,点击后弹出完整的图标选择器,让用户从 Material Icons 里选择任意图标。

颜色方面,可以加一个"自定义颜色"按钮,点击后弹出颜色选择器,让用户选择任意颜色。Flutter 有 flutter_colorpicker 这样的库可以用。

不过对于房间管理这个场景,预设的选项应该够用了,不需要太复杂。

小结

添加房间页面的核心是图标选择器和颜色选择器的实现。通过 Wrap 布局让选项自动换行,通过状态管理实现选中效果和预览联动。

预览区域是这个页面的亮点,让用户在保存前就能看到最终效果,提升了用户体验。

下一篇会讲房间详情页面的实现,展示房间里的家具列表,以及一些统计信息。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

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

更多推荐