EventAndNestedScroll
原理&流程
一. ns child
会在收到DOWN
事件时,找到自己祖上中最近的能与自己匹配的ns parent
,与它进行绑定并关闭它的事件拦截机制
二. 然后ns child
会在接下来的MOVE
事件中判定出用户触发了滑动手势,并把事件流拦截下来给自己消费
三. 消费事件流时,对于每一次MOVE
事件增加的滑动距离:
ns child
并不是直接自己消费,而是先把它交给ns parent
,让ns parent
可以在ns child
之前消费滑动dispatch/onNestedPreScroll()
- 如果
ns parent
没有消费或是没有消费完,ns child
再自己消费剩下的滑动dispatchNestedScroll()
- 如果
ns child
自己还是没有消费完这个滑动,会再把剩下的滑动交给ns parent
消费onNestedScroll()
- 最后如果滑动还有剩余,
ns child
可以做最终的处理dispatchNestedScroll()
四. 同时在ns child
的computeScroll()
方法中,ns child
也会把自己因为用户fling
操作引发的滑动,与上一条中用户滑动屏幕触发的滑动一样,使用「parent -> child -> parent -> child」的顺序进行消费
ns child
使用更灵活的方式找到和绑定自己的ns parent
,而不是直接找自己的上一级结点
ns child
在DOWN
事件时关闭ns parent
的事件拦截机制单独用了一个 Flag 进行关闭,这就不会关闭ns parent
对其他手势的拦截,也不会递归往上关闭祖上们的事件拦截机制。ns child
直到在MOVE
事件中确定自己要开始滑动后,才会调用requestDisallowInterceptTouchEvent(true)
递归关闭祖上们全部的事件拦截
对每一次MOVE
事件传递来的滑动,都使用「parent -> child -> parent -> child」机制进行消费,让ns child
在消费滑动时与ns parent
配合更加细致、紧密和灵活
对于因为用户fling
操作引发的滑动,与用户滑动屏幕触发的滑动使用同样的机制进行消费,实现了完美的惯性连续效果
DOWN事件来时,NSCstartNestedScroll()
找到自己祖上匹配的NSP进行绑定并关闭NSP的拦截;
MOVE事件来时:
「parent -> child -> parent -> child」的顺序进行消费
NSCdispatchNestedPreScroll()
中触发NSPonNestedPreScroll()
预先进行滑动事件的消费,之后将剩余事件返回;接着NSC消费剩余事件;
NSC消费事件后再dispatchNestedScroll()
去将剩余事件传递给NSPonNestedScroll()
消费。
NSP消费完剩余事件返回NSC,NSC此时可以做最后的处理,比如overScroll效果比如在 fling 的时候停止scroller
与MOVE
事件中处理滑动按照这个顺序进行消费:「dispatchNestedPreScroll()
到Parent -> 自己 -> dispatchNestedScroll()
-> 自己」一样,惯性滑动按照上面的顺序。
Case分析
ScrollView嵌套ScrollView
默认情况:外部ScrollView优先获取上下滑动的权力,在蓝色区域上下滑动,内部ScrollView并不会上下滑动
简述:
默认内部ScrollView会消费DOWN事件
默认外部ScrollView优先获取MOVE事件处理机会,并且当yDiff > mTouchSlop时,外部ScrollView会拦截掉MOVE事件并给内部ScrollView发CANCEL事件。
表现:(不考虑后来 Google 给它加的NestedScroll
开关),嵌套ScrollView同向滑动的效果如上,内部ScrollView优先获得DOWN事件的处理机会,外部的ScrollView优先获得MOVE事件的处理机会并且拦截(yDiff > mTouchSlop(8,可配置常量))。
分析:
当外部ScrollView嵌套内部ScrollView时,DOWN事件在内部ScrollView的onTouchEvent中返回true,内部ScrollView处理了DOWN事件。MOVE事件首先到达外部ScrollView的onInterceptTouchEvent方法,当滑动距离大于mTouchSlop时,会拦截掉MOVE事件,给内部ScrollView发出CANCEL事件,从而外部ScrollView获得了事件的处理权,内部ScrollView失去了事件的处理权。
ScrollView 实现了ViewParent接口,而ViewParent接口声明了和NestedScrollParent一样的接口方法
ScrollView 继承于View,而View实现了NestedScrollChild接口方法。
(看这就知道了,整体滑动嵌套的思路是NSC先获取事件处理优先权,也许是通过禁止外部拦截的方法获取的,自身的滑动事件消费剩余的滑动距离再由NSC在自身的dispatchNestedXXX中,通知NSP的onNestesXXScroll方法承接,所以内部是主动,外部是被动。至于惯性扔动事件类似?)
如果要达到外层ScrollView可以消费内部溢出的滑动和Fling事件的话,需要外层实现NestedScrollingParent,内层实现NestedScrollingChild协作事件处理。
???:
1、ScrollView覆盖了View的onTouchEvent并默认返回true;所以如果一个ScrollView外部还嵌套了其他ViewGroup了,则Down事件不会返回到外部,也同时MOVE、UP事件也随DOWN事件被ScrollView或其子类内部消费。
2、外ScrollView嵌套内ScrollView,内ScrollView包裹一个内部View的话,不打开(残缺的)嵌套滑动开关setNestedScrollingEnabled(true)
的话,点击内部View拖动:
那么DOWN事件会由外ScrollView传到内ScrollView再传到内部View,内部View不消费,回传到内ScrollView消费;
MOVE事件diff超过touchSlop
的话会被外ScrollView拦截处理,并产生一个CANCEL事件交给内SCrollView,因为DOWN已经被内ScrollView消费了,所以CANCEL事件也交给了内ScrollView消费,而不会再给到内部View。
UP事件只被外ScrollView分发并传递给外ScrollView的onTouchEvent消费,是的没有拦截。原因未知(理论上应该跟CANCEL一样才对…….)
3、不用小心翼翼地让改动尽量小,既然内部优先,完全可以让内部的ScrollView
在DOWN
事件的时候就申请外部不拦截,然后在滑动一段距离后,如果判断自己在该滑动方向无法滑动,再取消对外部的拦截限制,像这样也只能实现滑动事件,对于惯性事件的处理是不到位的。
1 | class SimpleNestedScrollView(context: Context, attrs: AttributeSet) : ScrollView(context, attrs) { |
ScrollView的真实源码实际上是直接在DOWN事件中请求了parent禁止拦截,然后自身先处理消耗MOVE事件直到达到自身的滑动边界之后再找自己的parent处理剩余滑动距离。对于惯性事件也是处理不到位的。
EventAndNestedScroll