问题描述
最近项目中碰到一个应用场景,就是竖向滑动的recyclerView A内嵌套着横向滑动的recyclerView B。并且recyclerView A和一个appbarLayout共同在一个CoordinatorLayout内协作滑动。然而这种复杂的嵌套在API 24下出bug了。如果触摸在recyclerView B上竖向滑动,那么recyclerView A是可以正常滑动的,但是appbarLayout却没有任何响应,也就是这时候nestedScrolling失效了。
执行过程
很显然在recyclerView B上竖向滑动其实是不会被B执行的(因为B是横向滑动的呀),那按道理竖向滑动就相当于在recyclerView A上竖向滑动的呀,为啥AppBarlayout不响应了,也就是NestedScrolling失效了?这种应用场景从逻辑上来看应该是正常的,肯定是android的SDK代码哪里出问题了。
我们来复盘一下整个流程。
- A dispatchTouchEvent ACTION_DOWN
- A onInterceptTouchEvent ACTION_DOWN 没有拦截
- B dispatchTouchEvent ACTION_DOWN
- B onInterceptTouchEvent ACTION_DOWN
- B onTouchEvent
- ACTION_DOWN流程结束
- A dispatchTouchEvent ACTION_MOVE
- A onInterceptTouchEvent ACTION_MOVE 拦截(因为是vertical的move)
- B dispatchTouchEvent ACTION_CANCEL 变成cancel因为上一步被拦截了
- [x] 没有B onInterceptTouchEvent,因为其已经是拦截链的末尾了
- B onTouchEvent ACTION_CANCEL
- A 成为拦截链尾端,A.targetView = null
- ACTION_MOVE 流程结束
- A dispatchTouchEvent ACTION_MOVE
- [x] 没有A onInterceptTouchEvent ACTION_MOVE 因为其已经是拦截链末尾了
- A onTouchEvent ACTION_MOVE
- ACTION_MOVE流程结束
- 重复上述A dispatchTouchEvent ACTION_MOVE流程....
- A dispatchTouchEvent ACTION_UP
- A onTouchEvent ACTION_UP
- ACTION_UP流程结束
问题所在
我们知道NestedScrolling机制的具体操作执行是在dispatchNestedPreScroll()调用的时候执行的。而是否可以执行是在startNestedScroll(nestedScrollAxis)中判断的,stopNestedScroll()强行关闭所有嵌套滑动。
经过查看recyclerView的源码可以发现,两个函数调用的时机如下:
- dispatchNestedPreScroll()
- 在onTouchEvent中ACTION_MOVE的时候被调用
- startNestedScroll()
- 在onInterceptTouchEvent中ACTION_DOWN的时候被调用,根据canXXScroll的轴来开启/关闭嵌套滑动
- 在onTouchEvent中ACTION_DOWN的时候被调用,根据canXXScroll的轴来开启/关闭嵌套滑动
- stopNestedScroll()
- 在onTouchEvent中MOTION_UP的时候被调用
- 在onTouchEvent中MOTION_CANCEL的时候被调用
有了这几个我们结合上述的流程可以发现,在B onTouchEvent ACTION_CANCEL的时候stopNestedScroll被调用,这时候嵌套滑动被关闭了。后续的A dispatchTouchEvent ACTION_MOVE在也没有机会把嵌套滑动机制打开,也就是所有的ACTION_MOVE都不会触发嵌套滑动,所以appbarLayout就死活不动了。
解决办法
知道了原因,我们只要手工在特定节点再调用一次startNestedScroll(SCROLL_AXIS_VERTICAL)即可,比较粗暴的做法就是不管什么时候都开着嵌套滑动。让recyclerView B继承一个自己的recyclerView如下:
public class MyRecyclerView extends RecyclerView {
//blabla....
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean res = super.dispatchTouchEvent(ev);
startNestedScroll(SCROLL_AXIS_VERTICAL);
return res;
}
}