SwipeRefreshLayout嵌套双层RecyclerView时刷新触发冲突的解决

版权声明:本文为博主原创文章,未经博主允许不得转载。 - long for us https://blog.csdn.net/longforus/article/details/72903473

今天在项目中遇到这样的一个场景:

<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/srl"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/common_background">

    <com.fec.kuaixiao.kuaixiaocore.view.EmptyLayout //封装的数据空,异常视图
        android:id="@+id/cmEmptyLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
     >
        <com.fec.feccommon.view.PTRecycleView  //封装常用方法的RecyclerView
            android:id="@+id/rv1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />
    </com.fec.kuaixiao.kuaixiaocore.view.EmptyLayout>
</android.support.v4.widget.SwipeRefreshLayout>

在上述布局的 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的上滚.

扫描二维码关注公众号,回复: 3256823 查看本文章

刚开始觉得这个问题比较难,没想到搞了3个小时竟然搞定了,还要多多学习.

猜你喜欢

转载自blog.csdn.net/longforus/article/details/72903473