今天在项目中遇到这样的一个场景:
在上述布局的 rv1的ItemView中有的会包含几张图片,也用同样的RecyclerView rv2配合GridLayoutManager(getContext(), 3)来展示.
实现后就出现了下图的问题:
即使rv1还没有下划到顶部,在嵌套的图片的rv2区域内下划都会触发srl的刷新操作,这个显然是不能接受的.
显然是滑动冲突的问题,但是个人对于滑动冲突的理解还很肤浅,我觉得可能是touchEvent传送到内部的rv2的时候.rv2没有消费跳过了外层的rv1直接把事件交给了srl.当然这个是异想天开的,没有任何依据.通过观察rv2的:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
3个方法发现并非每次在rv2滑动都会触发这些方法,想从这里看出问题是看不出来了.可能是SwiperRefreshLayout对touchEvent的分发有特殊的处理.待验证.
后来百度找到的解决方法有设置rv2的onTouchListener 动态的设置srl的enabled,这个方法我未能实现.设置的Listener并未按预想被调用.
其他比较符合问题描述的是:
关于SwipeRefreshLayout和ListView滑动冲突问题解决办法 这篇文章提到了在SwiperRefreshLayout的onInterceptTouchEvent中的:canChildScrollUp()这个方法在上面的场景中这个方法 最终会执行 ViewCompat.canScrollVertically(mTarget, -1); 返回false, 不拦截事件,但是事件传递下去如何最终又回传触发了刷新.这一过程我还没有根据源码走下去,不甚了解.
if (!isEnabled() || mReturningToStart || canChildScrollUp()
|| mRefreshing || mNestedScrollInProgress) {
// Fail fast if we're not in a state where a swipe is possible
return false;
}
public boolean canChildScrollUp() {
if (mChildScrollUpCallback != null) {
return mChildScrollUpCallback.canChildScrollUp(this, mTarget);
}
if (android.os.Build.VERSION.SDK_INT < 14) {
if (mTarget instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mTarget;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(mTarget, -1);
}
}
文章的解决方法是继承SwiperRefreshLayout传入ListView,复写canChildScrollUp()方法,{可能作者没有注意到
if (mChildScrollUpCallback != null) {
return mChildScrollUpCallback.canChildScrollUp(this, mTarget);
}
这行代码,其实只要传入OnChildScrollUpCallback的实现就可以接管canChildScrollUp()方法的处理,不用继承复写,}在这个方法中根据ListView是否可滚动,决定SwiperRefreshLayout是否拦截这个事件,看到这里我就想到了一个可能解决我的问题的方法.
为SwiperRefreshLayout设置OnChildScrollUpCallback的实现接管canChildScrollUp()方法的处理,并根据我的rv1的状态返回合适的值.
开始我以为SwiperRefreshLayout中的mTarget就是我当前点击的rv2,后来发现mTarget是当前SwiperRefreshLayout的第一子View也就是布局中的EmptyLayout,它是不能滚动的canScrollVertically自然返回false了,canChildScrollUp()方法也就没有能影响到事件的拦截,
最后用rv1代替mTarget去计算是否还可以滚动到顶部就能解决问题了.
srl.setOnChildScrollUpCallback(
(parent, child) -> ViewCompat.canScrollVertically(rv1, -1));
这样就解决了问题,在rv1还可以向上滚动的时候,返回true, SwiperRefreshLayout的onInterceptTouchEvent返回false,事件继续传递触发了rv1的上滚.
刚开始觉得这个问题比较难,没想到搞了3个小时竟然搞定了,还要多多学习.