请添加图片描述

房间详情页面是从房间列表点进来的,展示这个房间里所有的家具,以及一些统计信息。这个页面用了 CustomScrollViewSliverAppBar,实现了一个可折叠的头部效果。

说实话,SliverAppBar 是 Flutter 里比较复杂的组件之一,但效果确实好看。头部展开时显示大图标和房间名,滚动时自动折叠成普通的 AppBar,用户体验很流畅。

页面设计思路

房间详情页面的设计要点:

  1. 可折叠的头部,展示房间图标和名称
  2. 统计卡片,显示家具数量和总价值
  3. 家具列表,展示这个房间里的所有家具
  4. 悬浮添加按钮,可以快速添加家具到这个房间

头部用房间的主题色作为背景,和房间列表页面的卡片颜色呼应,用户一眼就能认出是哪个房间。

页面基础结构

房间详情页面用 StatelessWidget

class RoomDetailPage extends StatelessWidget {
  const RoomDetailPage({super.key});

在 build 方法里获取路由参数:

  
  Widget build(BuildContext context) {
    final room = Get.arguments as Map<String, dynamic>? ?? {
      'name': '客厅', 
      'icon': Icons.weekend, 
      'count': 12, 
      'total': 35600.0, 
      'color': const Color(0xFF8B4513)
    };

Get.arguments 获取从房间列表传过来的房间数据。如果没有传参数,就用默认的客厅数据,方便开发调试。

家具列表数据暂时写死:

    final furnitures = [
      {'name': '北欧实木沙发', 'price': 12800.0, 'date': '2024-01-15'},
      {'name': '茶几', 'price': 2800.0, 'date': '2024-01-15'},
      {'name': '电视柜', 'price': 3500.0, 'date': '2023-12-20'},
      {'name': '落地灯', 'price': 680.0, 'date': '2023-11-10'},
    ];

实际项目中,这个列表应该根据房间 ID 从数据库查询。

CustomScrollView 结构

页面主体用 CustomScrollView

    return Scaffold(
      backgroundColor: const Color(0xFFFAF8F5),
      body: CustomScrollView(
        slivers: [
          SliverAppBar(
            expandedHeight: 180.h,
            pinned: true,
            backgroundColor: room['color'] as Color,
            foregroundColor: Colors.white,

CustomScrollView 是 Flutter 里用来实现复杂滚动效果的组件,它的子组件必须是 Sliver 系列组件。

SliverAppBar 是可折叠的 AppBar,expandedHeight 设置展开时的高度,pinned: true 表示折叠后 AppBar 会固定在顶部。

背景色用房间的主题色,这样每个房间的详情页颜色都不一样。

FlexibleSpaceBar 实现

SliverAppBarflexibleSpace 参数用来设置可折叠区域的内容:

            flexibleSpace: FlexibleSpaceBar(
              title: Text(room['name'] as String),
              background: Container(
                color: (room['color'] as Color).withOpacity(0.2),
                child: Center(
                  child: Icon(
                    room['icon'] as IconData, 
                    size: 60.sp, 
                    color: Colors.white.withOpacity(0.3)
                  )
                ),
              ),
            ),
            actions: [
              IconButton(icon: const Icon(Icons.edit), onPressed: () {})
            ],
          ),

FlexibleSpaceBartitle 会在折叠过程中自动调整位置和大小,展开时在底部居中,折叠后移到左边变小。

background 是展开时显示的背景内容,这里放了一个大的半透明图标,作为装饰。图标透明度 0.3,不会太抢眼。

右上角有个编辑按钮,点击可以编辑房间信息。

页面内容区域

SliverAppBar 下面是页面的主要内容:

          SliverToBoxAdapter(
            child: Padding(
              padding: EdgeInsets.all(16.w),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  _buildStatsRow(room),
                  SizedBox(height: 20.h),
                  Text('房间家具', style: TextStyle(
                    fontSize: 18.sp, 
                    fontWeight: FontWeight.bold, 
                    color: const Color(0xFF5D4037)
                  )),
                  SizedBox(height: 12.h),
                  ...furnitures.map((f) => 
                    _buildFurnitureItem(f, room['color'] as Color)
                  ).toList(),
                ],
              ),
            ),
          ),
        ],
      ),

SliverToBoxAdapter 用来把普通的 Widget 转换成 Sliver,这样就能放到 CustomScrollView 里了。

内容包括统计卡片、"房间家具"标题、家具列表。家具列表用展开运算符 ... 把 map 结果展开。

悬浮添加按钮

右下角的添加按钮:

      floatingActionButton: FloatingActionButton(
        onPressed: () => Get.toNamed(AppRoutes.addFurniture),
        backgroundColor: room['color'] as Color,
        child: const Icon(Icons.add, color: Colors.white),
      ),
    );
  }

按钮颜色用房间的主题色,和页面整体风格统一。点击后跳转到添加家具页面,实际项目中应该带上房间 ID,这样添加的家具会自动关联到这个房间。

统计卡片组件

统计卡片显示家具数量和总价值:

  Widget _buildStatsRow(Map<String, dynamic> room) {
    return Container(
      padding: EdgeInsets.all(16.w),
      decoration: BoxDecoration(
        color: Colors.white, 
        borderRadius: BorderRadius.circular(16.r)
      ),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          _buildStatItem('家具数量', '${room['count']}件'),
          Container(width: 1, height: 40.h, color: Colors.grey[200]),
          _buildStatItem('总价值', '¥${(room['total'] as double).toStringAsFixed(0)}'),
        ],
      ),
    );
  }

卡片里有两个统计项,中间用一条竖线分隔。spaceAround 让两个统计项均匀分布。

竖线用 Container 实现,宽度 1,高度 40,颜色是浅灰色。这种分隔线比用 Divider 更灵活,可以精确控制尺寸。

统计项组件

单个统计项的实现:

  Widget _buildStatItem(String label, String value) {
    return Column(
      children: [
        Text(value, style: TextStyle(
          fontSize: 18.sp, 
          fontWeight: FontWeight.bold, 
          color: const Color(0xFF5D4037)
        )),
        SizedBox(height: 4.h),
        Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 12.sp)),
      ]
    );
  }

数值在上面,用大字号加粗显示;标签在下面,用小字号灰色显示。这种上下结构的统计项很常见,信息层次清晰。

家具列表项组件

每个家具是一个卡片:

  Widget _buildFurnitureItem(Map<String, dynamic> furniture, Color color) {
    return Container(
      margin: EdgeInsets.only(bottom: 10.h),
      padding: EdgeInsets.all(14.w),
      decoration: BoxDecoration(
        color: Colors.white, 
        borderRadius: BorderRadius.circular(12.r)
      ),
      child: Row(
        children: [
          Container(
            padding: EdgeInsets.all(10.w),
            decoration: BoxDecoration(
              color: color.withOpacity(0.1), 
              borderRadius: BorderRadius.circular(10.r)
            ),
            child: Icon(Icons.chair, color: color, size: 24.sp),
          ),

左边是家具图标,用房间的主题色。这样整个页面的颜色都是统一的,视觉效果很协调。

图标容器的背景色是主题色加 0.1 透明度,和房间列表页面的卡片风格一致。

家具信息和价格

家具名称、日期和价格:

          SizedBox(width: 12.w),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start, 
              children: [
                Text(furniture['name'] as String, style: TextStyle(
                  fontWeight: FontWeight.w600, 
                  fontSize: 15.sp
                )),
                Text(furniture['date'] as String, style: TextStyle(
                  color: Colors.grey[600], 
                  fontSize: 12.sp
                )),
              ]
            )
          ),
          Text(
            '¥${(furniture['price'] as double).toStringAsFixed(0)}', 
            style: TextStyle(color: color, fontWeight: FontWeight.bold)
          ),
        ],
      ),
    );
  }

中间是家具名称和购买日期,用 Expanded 包裹让它占据剩余空间。右边是价格,用房间主题色显示。

价格用 toStringAsFixed(0) 转成整数,不显示小数点。

SliverAppBar 的工作原理

SliverAppBar 的折叠效果是这样实现的:

  1. 初始状态,AppBar 高度是 expandedHeight,显示完整的 flexibleSpace 内容
  2. 用户向上滚动时,AppBar 高度逐渐减小,flexibleSpace 内容逐渐隐藏
  3. 当高度减小到普通 AppBar 高度时,停止折叠(因为 pinned: true
  4. 继续滚动时,AppBar 固定在顶部,只显示标题和按钮

FlexibleSpaceBartitle 会在这个过程中自动调整位置,从底部居中移动到左边,字号也会变小。这个动画是 Flutter 自动处理的,不需要手动实现。

为什么用 CustomScrollView

普通的 Scaffold + AppBar 没法实现这种折叠效果。CustomScrollView 提供了一个统一的滚动上下文,让 SliverAppBar 和下面的内容能够协调滚动。

CustomScrollView 的子组件必须是 Sliver 系列:

  • SliverAppBar:可折叠的 AppBar
  • SliverList:列表
  • SliverGrid:网格
  • SliverToBoxAdapter:把普通 Widget 转成 Sliver

这个页面内容不多,用 SliverToBoxAdapter 包一个 Column 就够了。如果家具列表很长,可以改成 SliverList 实现懒加载。

颜色主题的传递

这个页面的颜色主题是从路由参数传过来的,包括:

  • AppBar 背景色
  • 悬浮按钮颜色
  • 家具图标颜色
  • 价格文字颜色

所有用到颜色的地方都用 room['color'],这样每个房间的详情页都有自己的主题色,和房间列表页面的卡片颜色一致。

这种设计让用户在不同页面之间切换时,能通过颜色快速识别当前是哪个房间。

小结

房间详情页面用 CustomScrollView + SliverAppBar 实现了可折叠的头部效果。头部展开时显示房间图标和名称,滚动时自动折叠。

页面内容包括统计卡片和家具列表,都用房间的主题色作为点缀,视觉效果统一。悬浮添加按钮方便用户快速添加家具。

下一篇会讲分类管理页面的实现,用列表展示所有家具分类,支持添加和编辑。


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

Logo

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

更多推荐