Nested Scrolling child/parent 1,2,3父子方法都是对应的,为何这么设计

40 阅读4分钟

为何成对 & 对应(协议化的原因)

  1. 握手模型清晰(谁发起、谁响应)

    • 手势的“主导者”是子视图(Child)。它用 dispatchNestedPreScroll/Scroll/PreFling/Fling/… 发起“请求”,父视图(Parent)用对应的 onNestedPreScroll/… 响应
    • 这种请求-响应结构让“是否拦、拦多少、何时拦”都有明确时机与返回值,避免隐式副作用。
  2. 阶段边界明确(pre → child → post)

    • pre:父优先机会(例如先折叠 AppBar);

    • 子:自己消耗剩余;

    • post:把“未消耗/已消耗”再告知父做事后处理

      配对 API 把每一阶段都落在成对回调里,保证顺序与责任明确。

  3. 双向可选 & 可级联

    • 不是所有 View 都必须当 Parent/Child,但任何 View 想参与协议时,只要实现各自接口即可。
    • 由于是成对方法,事件可以沿 父系链逐级协商(祖父也可接力),不会把实现绑死在某个容器。
  4. 版本演进的语义兼容

    • v1 ↔ v2 ↔ v3 的接口族(Child1/2/3、Parent1/2/3)一一对应,便于渐进式增强

      • v1:无类型区分,无法正确表达 fling;
      • v2:增加 type = TOUCH/NON_TOUCH,触摸与惯性两路都能协作;
      • v3:在 post 阶段增加“父二次消耗回传”(consumed[] 可写),实现更细腻同步。
    • 成对命名让新旧并存时不混淆,老代码可留在 v1/v2,新代码用 v3 能“更顺滑”。

这么设计的好处(工程与体验双赢)

A. 正确性 & 可预测性

  • 确定时序:谁先、谁后、谁还能再吃一口(v3)都写在对应的方法里,行为可推理、易复现。

  • 归责清晰:滚动被谁“吃掉了多少”通过 consumed[] 明确回传,减少“为什么 AppBar 有时不收/忽然弹”的黑箱问题。

  • 支持多层父系:逐层 dispatch / onNested…,天然支持复杂控件树而不靠 hack。

B. 组合复用 & 解耦

  • 面向组合而非继承:任何 View 想当 Parent/Child 都行;RecyclerView 当 Child、AppBarLayout 当 Parent,或你自定义容器二者兼具,互不耦合

  • 行为可插拔:CoordinatorLayout 通过 Behavior 把 Parent 逻辑模块化;协议化接口让第三方容器也能无缝接入。

C. 性能与实现细节友好

  • 零或低分配:consumed[]/offsetInWindow[] 用可复用数组返值,避免频繁对象分配。

  • 类型与轴向过滤:type(触摸/非触摸)与 axes(横/纵)让无关方直接快速拒绝,少做无效计算。

  • 动画帧内可协作:TYPE_NON_TOUCH 让 fling 的每一帧也走同样的 pre/post 协商,父子动画保持物理一致性(不“脱节”)。

D. 可演进 & 可测试

  • 版本对齐:Child2 ↔ Parent2、Child3 ↔ Parent3 的一一对应,使能力升级时易于落地与回退。

  • 可单测:预期输入(dy、type)和预期输出(consumed、unconsumed)都显式,便于写单元/集成测试验证联动边界。

v1 / v2 / v3 的“对应”各自解决了什么?

  • v1(入门) :只有“滚或不滚”的粗粒度,不知道是手指拖还是 fling,联动很容易在松手后“断联”。

  • v2(分型) :通过 type 把 触摸惯性 分开,两条路径都能 pre/post,AppBar 与 RecyclerView 的 fling 联动才真正稳定。

  • v3(回写) :onNestedScroll(..., consumedByParent[]) 允许父在 post 阶段再吃一口并同步给子,避免“子已滚到边但父还想再收/放一点”导致的细微跳变,手感更丝滑。

一张“方法对照表”(理解成协议握手)

Child 发起Parent 响应作用
startNestedScroll(axes, type)onStartNestedScroll / onNestedScrollAccepted建立会话,声明意图
dispatchNestedPreScroll(dx, dy, …, type)onNestedPreScroll父优先消耗
(Child 自己滚)自主消费剩余
dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, …, type[, consumedByParent])onNestedScroll(v3 可回写 consumed)父事后再处理(v3 更细腻)
dispatchNestedPreFling(vx, vy)onNestedPreFling父优先消耗惯性
dispatchNestedFling(vx, vy, consumed)onNestedFling告知父“我已/未启动 fling”
stopNestedScroll(type)onStopNestedScroll结束会话,收尾

一句总结

成对的 Child/Parent 方法把“嵌套滚动”变成了可协商、可分阶段、可演进的协议。它带来明确的时序与责任稳定的联动与手感强组合性与低耦合,并允许 Android 团队在 v1→v2→v3 中逐步增强而不破坏旧生态