You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Flutter实现类似Talabat应用的餐厅详情页滚动效果

效果参考示意图

实现方案

该效果核心为头部可折叠+分类栏吸顶+左右列表滚动联动,以下分两种常见开发场景给出可落地实现逻辑:

1. Web/H5 端实现

核心实现步骤

  • 页面结构拆分3个模块:
    • 顶部餐厅详情模块:包含头图、名称、评分、配送信息等内容,无特殊定位属性,随页面滚动自然收起
    • 分类导航栏:添加position: sticky+top: 0属性,滚动到视口顶部后自动吸顶
    • 内容展示区:flex布局分为左右两栏,左侧为分类列表,右侧为对应分类的餐品列表
  • 滚动联动逻辑:
    • 给右侧餐品滚动容器绑定滚动监听,计算每个餐品分类区块距离容器顶部的偏移量,当偏移量小于当前滚动距离时,同步高亮左侧对应分类选项
    • 点击左侧分类时,调用scrollIntoView方法将右侧对应分类区块滚动到可视区域,可配置behavior: 'smooth'实现平滑滚动效果
    • 用节流函数包装滚动监听回调,滚动触发间隔控制在100ms左右,避免性能损耗
  • 核心代码片段
/* 分类栏吸顶样式 */
.category-nav {
  position: sticky;
  top: 0;
  z-index: 99;
  background: #ffffff;
}
/* 右侧餐品滚动容器 */
.food-wrapper {
  height: calc(100vh - 48px); /* 48px为分类栏高度 */
  overflow-y: auto;
}
// 滚动高亮逻辑简化版
const handleScroll = throttle(() => {
  const scrollTop = document.querySelector('.food-wrapper').scrollTop
  document.querySelectorAll('.food-group').forEach((group, index) => {
    if (group.offsetTop <= scrollTop + 10) {
      // 同步高亮左侧对应分类
      document.querySelectorAll('.category-item')[index].classList.add('active')
    }
  })
}, 100)

2. Flutter 端实现

核心实现步骤

  • 外层用NestedScrollView实现头部餐厅详情的折叠效果,headerSliverBuilder中放置餐厅详情对应的Sliver组件,设置pinned: false,滚动时可完全收起
  • 分类导航栏用SliverPersistentHeader实现,设置pinned: true,滚动到顶部后固定在视口顶部
  • 正文区域用SliverToBoxAdapter包裹左右联动列表,左侧为分类ListView,右侧为餐品分组ListView
  • 联动逻辑通过ScrollController实现:
    • 给右侧餐品列表绑定滚动控制器,监听滚动偏移,计算每个餐品分组的位置,同步更新左侧分类选中状态
    • 点击左侧分类时,计算右侧对应分组的滚动偏移量,调用animateTo方法滚动到对应位置
  • 核心代码片段
// 吸顶分类栏实现示例
SliverPersistentHeader(
  pinned: true,
  delegate: _StickyHeaderDelegate(
    child: const CategoryTabBar(),
    height: 48,
  ),
)

// 自定义StickyHeaderDelegate
class _StickyHeaderDelegate extends SliverPersistentHeaderDelegate {
  final Widget child;
  final double height;
  _StickyHeaderDelegate({required this.child, required this.height});

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

  @override
  double get maxExtent => height;

  @override
  double get minExtent => height;

  @override
  bool shouldRebuild(covariant _StickyHeaderDelegate oldDelegate) {
    return oldDelegate.child != child || oldDelegate.height != height;
  }
}

内容的提问来源于stack exchange,提问作者Owais

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 20:06:03