嵌套滑动一直是Android中比较棘手的问题, 根本原因是Android的事件分发机制导致的.不了解事件分发机制的同学可以先看看一点见解: Android事件分发机制, 导致嵌套滑动难处理的关键原因在于当子控件消费了事件, 那么父控件就不会再有机会处理这个事件了, 所以一旦内部的滑动控件消费了滑动操作, 外部的滑动控件就再也没机会响应这个滑动操作了
不过这个问题终于在LOLLIPOP(SDK21)之后终于有了官方的解决方法, 就是嵌套滑动机制. 在分析具体的代码逻辑之前, 下面先简单介绍下嵌套滑动的一些基本知识.
嵌套滑动机制可以理解为一个约定, 原生的支持嵌套滑动的控件都是依据这个约定来实现嵌套滑动的, 例如CoordinatorLayout, 所以如果你自定义的控件也遵守这个约定, 那么就可以跟原生的控件进行嵌套滑动了
嵌套滑动的基本原理是在子控件滑动一段距离的请求时,先询问父控件是否要滑动,如果滑动了父控件就通知子控件它消耗了一部分滑动距离,子控件就只处理剩下的滑动距离,然后子控件滑动完毕后把剩余的滑动距离传给父控件。
嵌套滑动的相关逻辑作为普通方法写进了View
和ViewGroup
类,例如dispatchNestedScroll()
官方在android.support.v4
兼容包中提供了两个接口NestedScrollingChild
和NestedScrollingParent
,还有两个辅助类NestedScrollingChildHelper
和NestedScrollingParentHelper
来帮助控件实现嵌套滑动.
这个兼容的原理很简单
两个接口NestedScrollingChild
和NestedScrollingParent
分别定义上面提到的View和ViewParent新增的普通方法
在代码中是通过ViewCompat
和ViewParentCompat
类来判断调用的方法是控件自有的方法, 还是接口的方法
如果版本是SDK21之前, 那么就会判断控件是否实现了接口, 然后调用接口的方法, 如果是SDK21之后, 那么就可以直接调用对应的方法.
接下来通过分析相对简单的支持嵌套滑动的容器NestedScrollView
来了解下怎样主动调起嵌套滑动的方法, 以及嵌套滑动的具体逻辑.
startNestedScroll
: 起始方法, 主要作用是找到接收滑动距离信息的外控件.
dispatchNestedPreScroll
: 在内控件处理滑动前把滑动信息分发给外控件.
dispatchNestedScroll
: 在内控件处理完滑动后把剩下的滑动距离信息分发给外控件.
stopNestedScroll
: 结束方法, 主要作用就是清空嵌套滑动的相关状态
setNestedScrollingEnabled
和isNestedScrollingEnabled
: 一对get&set方法, 用来判断控件是否支持嵌套滑动.
dispatchNestedPreFling
和dispatchNestedFling
: 跟Scroll的对应方法作用类似, 不过分发的不是滑动信息而是Fling信息.(这个Fling好难翻译.. =。=)本文主要关注滑动的处理, 所以后续不分析这两个方法.
因为内控件是发起者, 所以外控件的大部分方法都是被内控件的对应方法回调的.
onStartNestedScroll
: 对应startNestedScroll
, 内控件通过调用外控件的这个方法来确定外控件是否接收滑动信息.
onNestedScrollAccepted
: 当外控件确定接收滑动信息后该方法被回调, 可以让外控件针对嵌套滑动做一些前期工作.
onNestedPreScroll
: 关键方法, 接收内控件处理滑动前的滑动距离信息, 在这里外控件可以优先响应滑动操作, 消耗部分或者全部滑动距离.
onNestedScroll
: 关键方法, 接收内控件处理完滑动后的滑动距离信息, 在这里外控件可以选择是否处理剩余的滑动距离.
onStopNestedScroll
: 对应stopNestedScroll
, 用来做一些收尾工作.
getNestedScrollAxes
: 返回嵌套滑动的方向, 区分横向滑动和竖向滑动, 作用不大
onNestedPreFling
和onNestedFling : 同上略