Nestedscroll和传统的手势有什么区别

41 阅读4分钟

1) 它们各自是什么

  • 传统手势机制

    指 dispatchTouchEvent → onInterceptTouchEvent → onTouchEvent 这一套 MotionEvent 分发/拦截模型。一个手势周期内,只能有一个最终消费方(通常是最内层的可滚控件;父通过 拦截 把事件抢回)。数据粒度是 原始触点事件

  • NestedScrolling(嵌套滑动协议)

    是一套在触摸系统之上工作的 滚动协作协议(Child ↔ Parent)。它关注的是 滚动位移/速度(dx, dy, vx, vy) ,而不是原始 MotionEvent。流程分 PreScroll(父先吃)→ 子消费 → PostScroll(把剩余交给父) ,并区分 TYPE_TOUCH / TYPE_NON_TOUCH(拖动/非触摸,如 fling)。v3 还支持 父在 Post 阶段回填“我又吃了一点”


2) 一眼看懂的对照表

维度传统手势(事件分发/拦截)NestedScrolling(嵌套滑动)
数据粒度MotionEvent(原始触点)dx/dy 位移、vx/vy 速度
消费模型单一消费者:最终只有一个 View 处理整段手势;父可在开始阶段拦截协同消费:父/子 同一帧可共同消费 同一个滚动量(Pre→子→Post)
目标决定“ 处理这次手势”决定“如何按比例分配 这次滚动”
时序钩子拦截前/分发后(拦截一次定生死)dispatchNestedPreScroll / dispatchNestedScroll / (Pre)Fling / stopNestedScroll(每帧协商)
多级父没有“向上反馈”机制;一旦确定消费者,父不再参与支持多级父链逐级消费,v3 支持父 回填 consumed 累加
Fling 协作各玩各的(通常子自己 fling,父无感)dispatchNestedPreFling / dispatchNestedFling 可 先问父 / 通知父 跟随或拦截
手势/动画区分无内建区分v2 起有 TYPE_TOUCH / TYPE_NON_TOUCH(拖动 vs fling/平滑滚动)
坐标修正自己处理父移动导致的坐标跳变offsetInWindow 提供位移修正
典型用途单层滚动、点击/拖拽、简单拦截冲突同向嵌套滚动协作:AppBar 收起与列表滚动、折叠头图跟随、底部面板与内容联动
学习/实现成本低:重写 2~3 个回调即可中:Child/Parent 双方都要实现(或用内置控件)并正确处理 pre/post/fling/type

3) 为什么需要 NestedScrolling(传统手势做不到/做不好)

  1. 同向嵌套共享滚动量:例如“上滑先收起 AppBar,再让 RecyclerView 滚”。传统拦截只能二选一:要么父全吃、要么子全吃;无法一帧按比例分配
  2. Fling 的协作:手指离开后列表继续惯性滚动,父(如 AppBar/头图)也应跟随;传统手势对 fling 无协作渠道。
  3. 多级父的逐级消费:子吃不完的 dy 交给父1,父1 吃不完再交父2……传统手势没有“post”阶段的回退通道。
  4. 触摸 vs 非触摸链路隔离:v2 的类型维度使“拖动链路”和“惯性/动画链路”互不干扰,避免奇怪的冲突。

4) 什么时候用谁(决策表)

  • 仅单层可滚(如一个 RecyclerView / ScrollView) → 传统手势足够。
  • 不同方向嵌套(外层竖、内层横) → 传统拦截 + 方向判定即可。
  • 同方向嵌套且要“先父再子/先子再父/按规则分配”上 NestedScrolling(v2/v3)
  • 手指放开后父也要跟随子惯性(AppBar/头图/底部面板) → 必须 NestedScrolling(含 Pre/后续 Fling)
  • Compose 项目 → 优先用 nestedScroll/NestedScrollConnection(它就是此协议在 Compose 的落地)。

5) 典型场景对比

  • AppBarLayout ↔ RecyclerView

    • 传统:要么 AppBar 抢走事件,要么 RV 独吃 → 体验割裂。
    • 嵌套:onNestedPreScroll 先吃 dy 收起 AppBar;子吃剩余;到顶/到底把 dyUnconsumed 交给另一侧继续;fling 用 (Pre)Fling 协同,跟手/跟惯性都顺畅。
  • NestedScrollView 包 RecyclerView(同向)

    • 传统:强拦截常引发布局抖动或滚动黏连。
    • 嵌套:通常让其中一层不滚(禁用 nested)或使用 父/子明确分工的 NestedScrolling,视交互决定谁先吃。

6) 性能与工程取舍

  • NestedScrolling 有更多回调(每帧 pre/post),但数据量只是几个整型,开销很小;真正的成本在“你做了哪些动画/测量/布局”。
  • 按需开始/结束:只在 ACTION_DOWN 调 startNestedScroll,在 UP/CANCEL stopNestedScroll;复用 consumed/offset 数组避免分配。
  • 区分 type:拖动走 TYPE_TOUCH,fling/平滑滚动走 TYPE_NON_TOUCH,避免“链路串线”。
  • 优先用现成组件:RecyclerView(Child3)和 CoordinatorLayout/AppBarLayout/NestedScrollView 已实现好大部分协议,自己只需实现缺的 Parent/行为。

7) 常见坑 & 速治

  1. 只实现 v1,fling 不生效 → 升到 v2(含 type),更稳是 v3(父可回填 consumed)。
  2. 两层都能竖向滚,抖动 → 明确分工:要么禁一层滚动,要么用 NestedScrolling 分配滚动量。
  3. 父移动导致触点跳变 → 用 offsetInWindow 修正。
  4. 忘记 stop → 下个手势被污染;务必成对调用 start/stopNestedScroll。
  5. 以为 NestedScrolling 会替代触摸系统 → 它是“在触摸系统之上”的滚动协作层,两者并不互斥。

8) 一句话总结

  • 传统手势解决“这次手势给谁处理”。
  • NestedScrolling解决“这次滚动怎么在父/子之间协作分配(含 fling)”。
  • 同向嵌套、联动折叠、惯性协作 → 用 NestedScrolling(v2/v3) ;其他简单场景,传统手势即可。