google在V21里面加入了几个兼容类,CoordinatorLayout,NestedScrollView,recyclerview都具有嵌套滚动的机制。
他们的效果如下图:
上面是CoordinatorLayout和NestedScrollView,其中CoordinatorLayout实现了NestedScrollingParent接口,其余两个实现了NestedScrollingChild接口,在NestedScrollView执行触摸操作的时候,会回调给CoordinatorLayout。
那么开始时看这套机制代码前,我们先了解什么是嵌套滚动呢,举个例子:
你问你父亲要了100块钱,然后你准备出去买一个冰激凌吃,由于你是一个好孩子,于是你在去买冰激凌的时候告诉你父亲,然后你父亲觉得一个冰激凌要不到100块,于是他就把你的100块拿了70块去,然后重新给你了30块,于是你花了20块买了一个冰激凌剩余的10元自己买了点其他零食,由于你是个好孩子,你回去之后告诉了你的父亲买了什么。
是不是一脸懵逼
那么嵌套滚动对应上面的例子方法就是:你就是子view 你支持嵌套滚动,100块是触摸的值,要执行买冰激凌前你告诉你了父亲,你父亲可以根据决定是否要扣留你的这100块钱,如果扣完了你就没法干啥事情了,如果没有扣完,于是把你剩下的给你,相当于通过回调,子view在滚动前会通知父view,然后父view可以通过逻辑来决定是否消耗触摸值,如果消耗那么可以执行相应的操作,如果不消耗子view继续执行它自己的逻辑和操作。
先从NestedScrollingParent,NestedScrollingChild接口开始看
看下这个两个接口的方法定义:
- NestedScrollingParent
onStartNestedScroll:
如果当前布局支持滚动,则在子view ACTION_DOWN 时会回调此方法,通知父亲表示嵌套滚动开始
onNestedScrollAccepted:
如果接受滚动则可以在滚动前做一些初始化操作
onStopNestedScroll:
在一次嵌套滚动完成了,此方法会调用,在子view ACTION_UP或ACTION_CANCEL 时会回调此方法
onNestedScroll:
在子view滚动中的时候,调用
onNestedPreScroll:
子view有触摸的值,在滚动之前会回调给父亲,父亲根据需要消费
onNestedFling:
子view正在处理抛的操作
onNestedPreFling:
子view有抛的值,在滚动之前会回调给父亲,父亲根据需要消费
getNestedScrollAxes:
滚动的方向
- NestedScrollingParentChild
setNestedScrollingEnabled:
激活嵌套滚动操作
isNestedScrollingEnabled:
是否已经激活嵌套滚动操作
startNestedScroll:
通知父亲滚动开始
stopNestedScroll:
通知父亲本次滚动完成
hasNestedScrollingParent:
判断是否有支持嵌套滚动的父亲
dispatchNestedScroll:
分发滚动值,会回调给给父亲的onNestedScroll
dispatchNestedPreScroll:
滚动之前分发滚动值,会回调给给父亲的onNestedPreScroll
dispatchNestedFling:
分发抛的值,会回调给给父亲的onNestedFling
dispatchNestedPreFling:
抛之前分发滚动值,会回调给给父亲的onNestedPreFlin
上面是两个接口方法,大致描述。
另外还有两个帮助处理回调逻辑的兼容类(API21之前)NestedScrollingChildHelper,NestedScrollingParentHelper在执行一些view相关操作的时候这两个类会用ViewCompat,ViewParentCompat这两个兼容视图,达到在早于5.0之前或之后可以嵌套滚动互操作性,具体怎么用后面会讲到
接下来看下CoordinatorLayout 和 NestedScrollView它们之间是怎么进行交互的,首先NestedScrollView是CoordinatorLayout的孩子,当我们手指触摸屏幕的时候,事件会到NestedScrollView里,这个时候NestedScrollView就会根据触摸的值来回调给CoordinatorLayout。
注意CoordinatorLayout在不设置Behavior的情况下是默认不拦截事件了的,看一下代码
会调用performIntercept方法判断是否需要拦截
这个方法里面首先对子view进行排序,然后获取是否设置了behavior(后面再说这是个什么东西),如果设置了会调用behavior里面的代理方法。
接着当我们手指触摸屏幕,事件分发到NestedScorllView的时候,那么整个嵌套滚动操作就开始了
首先NestedScorllView会实例化两个帮助类,NestedScrollingParentHelper和NestedScrollingChildHelper,然后激活嵌套滚动
接着在事件传递给NestedScorllView,并且它自己处理的时候,在onTouchEvent DOWN会调用startNestedScroll:
点进去startNestedScroll看:
最终是在NestedScrollingChildHelper帮助类里面调用的,继续看帮助类里面的startNestedScroll:
首先判断是否能嵌套滚动,然后判断是否激动了滚动,然后在循环找支持嵌套滚动的父亲,如果当前支持滚动就调用滚动开始方法,并且返回,这里用的兼容视图ViewParentCompat里面方法startNestedScroll:
其实本质上就是调用了父亲的onStartNestedScroll,我们这里就是CoordinatorLayout的onStartNestedScroll了
然后接着,就是滚动操作了,接下来就要到ACTION_MOVE 方法体里面去了:
首先会计算偏移量,然后调用dispatchNestedPreScroll,这个方法其实就是调用了CoordinatorLayout的OnNestedPreScroll表示我要开始滚动了,我先发给父亲看父亲有没有消费这次的滚动值,点进去方法可以看到其实调用的也是帮助类的,那么最终我去帮助类里面看看方法的实现:
首先判断判断是否可以嵌套滚动,获取偏移量,然后就最终调用了父亲的onNestedPreScroll了,在代码的300行,这个时候我们就可以在父亲的回调里面根据页面逻辑处理偏移量了,是否消费或者不消费,consumed[0]代表X轴,consumed[1]代表Y轴。
然后接着代码会根据dispatchNestedPreScorll的返回值,计算还有多少偏移量没有被消费,接着进入到下面的if代码体,如果不是拖动状态,且剩余的偏移量大于最小触摸偏移量,就会进入到代码体里面,然后就进入到拖动状态,接着代码下就会调用dispatchNestedScroll,最终会调用父亲的onNestedScroll:
上面的234行会回调父亲的onNestedScroll告诉父亲我现在还要滚动这些偏移量,到这儿整个move的就完了
接着在ACTION_UP代码体里面会处理dispatchNestedPreFling和dispatchNestedFling最终也会回调给父亲,也和上面一样可以选择消费或者不消费:
最后还会调用stopNestedScroll,并且释放和回收资源,到此整体机制就结束了
结合上面的代码,我们知道我们可以在父亲的回调方法里面选择消费或者不消费,如果消费完了子view 不会执行任何操作,如果没消费完子view还会消费剩余的,我们可以在父亲的相对于的回调方法里面做一些页面上的滚动啊,放大缩小等。
可以说这套机制也是建立在事件分发基础上的,但是它比事件分发更灵活,我们只需要实现NestedScrollingParent和NestedScrollingChild,然后只需要关心OnNestedPreScroll,OnNestedScroll,OnNestedPreFling,OnNestedfling ,其余的可以交给帮助代理类NestedScrollingChildHelper,NestedScrollingParentHelper我们只需要了解原理即可。
后面需用这套机制去自定义一个类似于最上面GIF图上面那样的效果
最后还有一个beahvior,后面记录