FSA全栈行动 FSA全栈行动
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr

公众号:FSA全栈行动

记录学习过程中的知识
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr
  • AndroidUI

  • Android第三方SDK

  • Android混淆

  • Android仓库

  • Android新闻

  • Android系统开发

  • Android源码

  • Android注解AOP

  • Android脚本

  • AndroidTv开发

  • AndroidNDK

  • Android音视频

  • Android热修复

  • Android性能优化

  • Android云游戏

  • Android插件化

  • iOSUI

  • iOS工具

  • iOS底层原理与应用

  • iOS组件化

  • iOS音视频

  • iOS疑难杂症

  • iOS之Swift

  • iOS之RxSwift

  • iOS开源项目

  • iOS逆向

  • Flutter开发

    • Dart - 抽象类的实例化
    • Flutter - 打印好用的Debug日志
    • Flutter - 混合开发
    • Flutter - 解决混合开发iOS脚本打包遇到的问题
    • Flutter - 低版本在iOS14上遇到的问题与解决方案
    • Flutter - 解决原生弹窗的触摸事件被Flutter响应的问题
    • Flutter - 实现列表上下拉切换header
    • Flutter - 获取ListView当前正在显示的Widget信息
    • Flutter - 列表滚动定位超强辅助库,墙裂推荐!🔥
    • Flutter - 快速实现聊天会话列表的效果,完美💯
    • Flutter - 聊天输入框更新文本时的必备优化点🔖
    • Flutter - 我给官方提PR,解决run命令卡住问题 😃
    • Flutter - 探索run命令到底做了什么 🤔
    • Flutter - 引擎调试(iOS篇)🛠
    • Flutter - 引擎调试bug到提交PR实战 🐞
    • Flutter - 船新升级😱支持观察第三方构建的滚动视图💪
    • Flutter - 瀑布流交替播放视频 🎞
    • Flutter - IM保持消息位置大升级(支持ChatGPT生成式消息) 🤖
    • Flutter - 滚动视图中的表单防遮挡 🗒
    • Flutter - 秒杀1/2曝光统计 📊
    • 一天内加入 Flutter 和 FlutterCandies 两大组织是什么体验 🧐
    • Flutter - 如何快速搓一个微信通讯录列表(azlist) 📓
    • Flutter - 混编项目集成Shorebird热更新🐦(安卓篇)
    • Flutter - 混编项目集成Shorebird热更新🐦(iOS篇)
    • Flutter - 解决返回原生页面时dispose方法未被触发的问题 🐞
    • Flutter - 升级3.19之后页面多次rebuild?🤨
    • Flutter - 热更新 Shorebird 1.0 正式版来了 🐦
    • Flutter - 使用Pigeon实现视频缓存插件 🐌
    • Flutter - 轻松搞定屏幕旋转功能 😎
    • Flutter - 解决Connection closed before full header was received
    • Flutter - 实现聊天键盘与功能面板的丝滑切换 🍻
    • Flutter - 支持观察NestedScrollView,兼容性更强 😈
    • Flutter - 聊天键盘与面板丝滑切换的强势升级 🍻
    • Flutter - 升级到3.24后页面还会多次rebuild吗?🧐
    • Flutter - 轻松实现PageView卡片偏移效果
    • Flutter - 轻松搞定炫酷视差(Parallax)效果
    • Flutter - 危!3.24版本苹果审核被拒!
    • Flutter - 子部件任意位置观察滚动数据
    • Flutter - iOS编译加速
    • Flutter - Xcode16 还原编译速度
    • Flutter - GetX Helper 助你规范应用 tag
    • Flutter - GetX Helper 如何应用于旧页面
    • Flutter - 聊天面板库动画生硬?这次让你丝滑个够
    • Flutter - 使用本地 DevTools 验证 SVG 加载优化
    • Flutter - 详情页 TabBar 与模块联动?秒了!
      • 一、前言
      • 二、结构
      • 三、功能实现
        • 1、配置观察
        • 2、处理观察结果
        • 3、点击跳转
      • 四、最后
  • 移动端
  • Flutter开发
LinXunFeng
2025-08-17
目录

Flutter - 详情页 TabBar 与模块联动?秒了!

欢迎关注微信公众号:[FSA全栈行动 👋]

# 一、前言

如图,这是个非常常见的功能,像淘宝、唯品会、朴朴等 App 的商品详情页都可以看到。

简陋的 Demo 可科学上网后在线体验: https://fluttercandies.github.io/flutter_scrollview_observer/#/detail (opens new window)

顺带提一嘴,朴朴(版本: V5.6.8-0-1c4424)的这个功能有个小 bug,在点击 Tab 的时候可以将对应模块滚动到 TabBar 下方,但是它在滑动时却是从列表的顶部开始计算的,所以在点击了第二个 Tab 后,列表往下稍微滑一点点,就会切回第一个 Tab 了,是故意的还是不小心?还是故意不小心呢?🤔

OK,接下来我们就来看看如何通过我的开源库快速实现该功能

https://github.com/fluttercandies/flutter_scrollview_observer (opens new window)

# 二、结构

页面结构很简单,主体为 ListView,DetailNavBar 内部是使用 TabBar 来实现的,将其盖在 ListView 的上方。

return Stack(
  children: [
    const DetailListView(),
    const Positioned(
      top: 0,
      left: 0,
      right: 0,
      child: DetailNavBar(),
    ),
  ],
);

# 三、功能实现

我们先来完成滚动过程中更新 DetailNavBar 的 index 的功能

# 1、配置观察

创建两个必要的控制器

ScrollController scrollController = ScrollController();

late ListObserverController observerController = ListObserverController(
  controller: scrollController,
)
    // 不缓存 item 的偏移量
    //
    // 由于我的 ListView 有动态加载的模块,导致模块的偏移量并非固定,
    // 所以这里设置为 false
    // 如果你的 ListView 的模块都是是固定的,则建议设置为 true,这样
    // 下次可以直接取值跳转,避免计算
    ..cacheJumpIndexOffset = false;

使用 ListViewObserver 包裹 ListView 进行观察

// ListView (没什么需要改动的,怎么实现都可以)
Widget resultWidget = ListView.separated(
  ...
);

// 使用 ListViewObserver 包裹 ListView 进行观察
resultWidget = ListViewObserver(
  // 观察控制器
  controller: state.observerController,
  // DetailNavBar 的高度
  dynamicLeadingOffset: () => state.navBarHeight,
  // 观察结果回调
  onObserve: logic.onObserveForListView,
  // 只监听处理第一层滚动通知,即 notification.depth == 0
  scrollNotificationPredicate: defaultScrollNotificationPredicate,
  child: resultWidget,
);

这里重点说两个参数

1、dynamicLeadingOffset: 用于设置计算可视区域的偏移量,如图中的绿线,一般情况下,判断 ListView 的 item 是否为第一个,主要是看它是否接触绿线,当 dynamicLeadingOffset 返回的值越大,绿线就越向下偏移,计算的可视区域变小,第一个 item 也随之变化。

所以这里的 dynamicLeadingOffset 返回了 DetailNavBar 的高度 state.navBarHeight,就是从 DetailNavBar 的下方开始计算可视区域,计算第一个 item。

2、scrollNotificationPredicate: 用于是否处理滚动通知,ListViewObserver 内部是监听滚动通知来触发观察的,但在一些场景下,如 item 内部有轮播,轮播在每次翻页时也会发出滚动通知,不过其 depth 大于 0,所以可以用来判断当前的滚动通知是否为被观察的 ListView 发出的, defaultScrollNotificationPredicate 是 Flutter 自带的方法,代码如下:

/// A [ScrollNotificationPredicate] that checks whether
/// `notification.depth == 0`, which means that the notification
/// did not bubble through any intervening scrolling widgets.
bool defaultScrollNotificationPredicate(ScrollNotification notification) {
  return notification.depth == 0;
}

为了不改变先前版本的行为,scrollview_observer 并没有将其设置做为默认值,需要手动配置~

# 2、处理观察结果

在 ListViewObserver 的 onObserve 回调方法中我们可以拿到观察结果,通过观察结果我们就可以计算出当前 DetailNavBar 应该设置的 index。

这里 scrollview_observer 已经将计算逻辑封装至 ObserverUtils.calcAnchorTabIndex 方法中,按如下调用即可。

void onObserveForListView(ListViewObserveModel result) {
  final navBarTabController = state.navBarTabController;
  if (navBarTabController == null) return;

  // 计算 TabBar 的下标
  final index = ObserverUtils.calcAnchorTabIndex(
    observeModel: result,
    tabIndexes: state.navBarTabs.map((e) => e.index).toList(),
    currentTabIndex: navBarTabController.index,
  );
  // 更新 TabBar 的下标
  updateNavBarTabIndex(index);
}

void updateNavBarTabIndex(int index) {
  final navBarTabController = state.navBarTabController;
  if (navBarTabController == null) return;
  navBarTabController.index = index;
}

传值说明

  • ListView 有 8 个模块,对应的下标就是 0 ~ 7
  • DetailNavBar 显示的是 模块 1、4、7 的 Tab,对应模块的下标就是 [0, 3, 6]

上述 calcAnchorTabIndex 方法中的 tabIndexes,传入的就是 [0, 3, 6],然后计算出 DetailNavBar 对应的 [0, 1 ,2]

# 3、点击跳转

void handleNavBarTabTap(int index) {
  if (!state.scrollController.hasClients) return;

  final tabModel = state.navBarTabs[index];
  final moduleIndex = tabModel.index;
  
  // 第一个模块,直接回到顶部
  if (moduleIndex == 0) {
    state.scrollController.jumpTo(0);
    return;
  }
  // 其它模块
  state.observerController.jumpTo(
    index: moduleIndex,
    offset: (_) => state.navBarHeight,
  );
}

点击跳转第一个模块时,我们是知道偏移量的,直接 scrollController.jumpTo(0) 即可,避免不必要的计算。

点击跳转其它模块时,使用 observerController.jumpTo,其中 offset 用于设置向下偏移多少,这里返回了 DetailNavBar 的高度 state.navBarHeight,即滚动到 DetailNavBar 的下方。

# 四、最后

通过上述示例的讲解,相信你对 scrollview_observer 的使用又更加清楚,开源不易,如果你也觉得这个库好用,请不吝给个 Star 👍 ,并多多支持!

GitHub: https://github.com/fluttercandies/flutter_scrollview_observer (opens new window)

本篇到此结束,感谢大家的支持,我们下次再见! 👋

#Dart#Flutter
上次更新: 2025/08/17, 06:54:19
Flutter - 使用本地 DevTools 验证 SVG 加载优化

← Flutter - 使用本地 DevTools 验证 SVG 加载优化

最近更新
01
Flutter - 使用本地 DevTools 验证 SVG 加载优化
08-07
02
AI - Gemini CLI 摆脱终端限制
07-27
03
Flutter - 聊天面板库动画生硬?这次让你丝滑个够
07-20
更多文章>
Theme by Vdoing | Copyright © 2020-2025 FSA全栈行动
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×