为何成对 & 对应(协议化的原因)
-
握手模型清晰(谁发起、谁响应)
- 手势的“主导者”是子视图(Child)。它用 dispatchNestedPreScroll/Scroll/PreFling/Fling/… 发起“请求”,父视图(Parent)用对应的 onNestedPreScroll/… 响应。
- 这种请求-响应结构让“是否拦、拦多少、何时拦”都有明确时机与返回值,避免隐式副作用。
-
阶段边界明确(pre → child → post)
-
pre:父优先机会(例如先折叠 AppBar);
-
子:自己消耗剩余;
-
post:把“未消耗/已消耗”再告知父做事后处理。
配对 API 把每一阶段都落在成对回调里,保证顺序与责任明确。
-
-
双向可选 & 可级联
- 不是所有 View 都必须当 Parent/Child,但任何 View 想参与协议时,只要实现各自接口即可。
- 由于是成对方法,事件可以沿 父系链逐级协商(祖父也可接力),不会把实现绑死在某个容器。
-
版本演进的语义兼容
-
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 中逐步增强而不破坏旧生态。