Flutter如何实现头部固定

要实现首页头部(搜索栏 + 分类栏)固定、商品列表滚动 的效果,核心是将「固定头部」和「可滚动商品列表」拆分,通过 Column + Expanded + ListView 组合实现,或使用 CustomScrollView + SliverAppBar(更优雅)。以下是两种实现方案,优先推荐第二种(Sliver 方案,贴合 Material 设计且性能更优)。

方案 1:基础方案(Column + Expanded)

原理:将固定头部放在 Column 的顶部,商品列表用 Expanded 包裹(占满剩余空间),仅列表部分滚动,头部保持固定。

修改 home_page.dart_buildHomeContent 方法:

dart

less 复制代码
// 首页内容构建(固定头部版)
Widget _buildHomeContent() {
  return Column(
    children: [
      // 👇 固定头部:搜索栏 + 分类栏
      Column(
        children: [
          // 顶部搜索栏(固定)
          Container(
            color: const Color(0xFFFC6721),
            padding: const EdgeInsets.fromLTRB(10, 20, 10, 10),
            child: Column(
              children: [
                Row(
                  children: [
                    Expanded(
                      child: Container(
                        height: 38,
                        decoration: BoxDecoration(
                          color: Colors.white,
                          borderRadius: BorderRadius.circular(19),
                        ),
                        child: const Row(
                          children: [
                            SizedBox(width: 10),
                            Icon(Icons.search, size: 18, color: Colors.grey),
                            SizedBox(width: 5),
                            Text(
                              '搜索你想要的宝贝',
                              style: TextStyle(fontSize: 14, color: Colors.grey),
                            ),
                          ],
                        ),
                      ),
                    ),
                    const SizedBox(width: 10),
                    const Icon(Icons.camera_alt, color: Colors.white, size: 22),
                  ],
                ),
              ],
            ),
          ),

          // 分类入口(固定)
          Container(
            height: 100,
            color: Colors.white,
            padding: const EdgeInsets.symmetric(horizontal: 5),
            child: GridView.count(
              physics: const NeverScrollableScrollPhysics(),
              crossAxisCount: 8,
              children: _categoryList.map((item) {
                return CategoryItem(
                  icon: item['icon']!,
                  title: item['title']!,
                );
              }).toList(),
            ),
          ),
        ],
      ),

      // 👇 可滚动商品列表(占满剩余空间)
      Expanded(
        child: ListView(
          physics: const BouncingScrollPhysics(), // 弹性滚动
          children: [
            const SizedBox(height: 5),
            // 推荐商品列表
            Column(
              children: _goodsList.map((goods) {
                return GoodsItem(
                  imageUrl: goods['image']!,
                  title: goods['title']!,
                  price: goods['price']!,
                  location: goods['location']!,
                  time: goods['time']!,
                );
              }).toList(),
            ),
          ],
        ),
      ),
    ],
  );
}

方案 2:优雅方案(CustomScrollView + Sliver)

原理:使用 CustomScrollView 管理滚动区域,通过 SliverPersistentHeader 实现头部固定,SliverList 承载商品列表,性能更优且支持头部吸顶 / 折叠扩展(如需)。

步骤 1:封装固定头部为 Sliver 组件

home_page.dart 中新增头部组件:

dart

less 复制代码
// 固定头部组件(Sliver版)
Widget _buildFixedHeader() {
  return SliverPersistentHeader(
    pinned: true, // 固定在顶部(关键)
    floating: false,
    delegate: _SliverHeaderDelegate(
      minHeight: 138, // 头部最小高度(搜索栏38+分类栏100)
      maxHeight: 138, // 头部最大高度(固定高度,不折叠)
      child: Column(
        children: [
          // 搜索栏
          Container(
            color: const Color(0xFFFC6721),
            padding: const EdgeInsets.fromLTRB(10, 20, 10, 10),
            child: Row(
              children: [
                Expanded(
                  child: Container(
                    height: 38,
                    decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.circular(19),
                    ),
                    child: const Row(
                      children: [
                        SizedBox(width: 10),
                        Icon(Icons.search, size: 18, color: Colors.grey),
                        SizedBox(width: 5),
                        Text(
                          '搜索你想要的宝贝',
                          style: TextStyle(fontSize: 14, color: Colors.grey),
                        ),
                      ],
                    ),
                  ),
                ),
                const SizedBox(width: 10),
                const Icon(Icons.camera_alt, color: Colors.white, size: 22),
              ],
            ),
          ),
          // 分类栏
          Container(
            height: 100,
            color: Colors.white,
            padding: const EdgeInsets.symmetric(horizontal: 5),
            child: GridView.count(
              physics: const NeverScrollableScrollPhysics(),
              crossAxisCount: 8,
              children: _categoryList.map((item) {
                return CategoryItem(
                  icon: item['icon']!,
                  title: item['title']!,
                );
              }).toList(),
            ),
          ),
        ],
      ),
    ),
  );
}

// Sliver头部代理类(必须)
class _SliverHeaderDelegate extends SliverPersistentHeaderDelegate {
  final double minHeight;
  final double maxHeight;
  final Widget child;

  _SliverHeaderDelegate({
    required this.minHeight,
    required this.maxHeight,
    required this.child,
  });

  @override
  double get minExtent => minHeight;

  @override
  double get maxExtent => maxHeight;

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return SizedBox.expand(child: child);
  }

  @override
  bool shouldRebuild(_SliverHeaderDelegate oldDelegate) {
    return minHeight != oldDelegate.minHeight ||
        maxHeight != oldDelegate.maxHeight ||
        child != oldDelegate.child;
  }
}

步骤 2:修改 _buildHomeContent 为 CustomScrollView

dart

less 复制代码
// 首页内容构建(Sliver固定头部版)
Widget _buildHomeContent() {
  return CustomScrollView(
    physics: const BouncingScrollPhysics(), // 弹性滚动
    slivers: [
      // 👇 固定头部
      _buildFixedHeader(),
      // 👇 商品列表
      SliverList(
        delegate: SliverChildBuilderDelegate(
          (context, index) {
            final goods = _goodsList[index];
            return GoodsItem(
              imageUrl: goods['image']!,
              title: goods['title']!,
              price: goods['price']!,
              location: goods['location']!,
              time: goods['time']!,
            );
          },
          childCount: _goodsList.length,
          // 列表顶部间距
          addFirstChildIndex: 0,
          addAutomaticKeepAlives: true, // 保持列表项状态
        ),
      ),
    ],
  );
}

核心效果说明

两种方案均实现:

  1. 搜索栏 + 分类栏固定在屏幕顶部,不会随商品列表滚动;
  2. 仅商品列表区域可滚动,滚动时头部始终可见;
  3. 方案 1 更简单(适合新手),方案 2 更优雅(Flutter 官方推荐的滚动布局方案,支持后续扩展头部折叠 / 吸顶动画)。

额外优化(可选)

  1. 头部阴影:给固定头部添加底部阴影,增强层次感:

dart

less 复制代码
// 在固定头部外层添加Container
Container(
  decoration: BoxDecoration(
    boxShadow: [
      BoxShadow(
        color: Colors.grey.shade200,
        blurRadius: 3,
        offset: const Offset(0, 2),
      ),
    ],
  ),
  child: // 原头部内容
)
  1. 适配刘海屏:在搜索栏顶部添加安全区域:

dart

less 复制代码
// 搜索栏外层包裹SafeArea
SafeArea(
  top: true,
  child: Container(
    color: const Color(0xFFFC6721),
    padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), // 移除原20px顶部padding
    // 原搜索栏内容
  ),
)

运行修改后的代码,即可看到头部完全固定,商品列表滚动时头部不会跟随移动,完美还原闲鱼 APP 的固定头部效果。

相关推荐
崔庆才丨静觅25 分钟前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅2 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼3 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax