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 的固定头部效果。

相关推荐
韩曙亮1 小时前
【Web APIs】JavaScript 执行机制 ( 单线程特点 | 同步任务与异步任务 | 同步先行、异步排队 | 事件循环机制 )
开发语言·前端·javascript·异步任务·同步任务·web apis·js 引擎
单调7771 小时前
npm你还了解多少
前端
码途进化论1 小时前
基于 Vue 2 + VXE Table 的超大规模表格渲染架构设计与性能优化方案
前端
漫天星梦1 小时前
iOS 手机无法播放视频问题排查与解决方案记录
前端·ios
好好好明天会更好1 小时前
uniapp项目中视频播放控制对象
前端·vue.js
萌狼蓝天1 小时前
[Vue2]项目中 vue-draggable-resizable 列宽拖动问题修复(首次拖动列宽突然变得很小)
前端·javascript·vue.js·前端框架·ecmascript
带带弟弟学爬虫__1 小时前
ks安卓—did注册
前端·javascript·vue.js·python·网络爬虫
维维酱2 小时前
使用 TRAE SOLO: 搭建前端项目脚手架
前端