1、解决RecyclerView与NestedScrollView的滑动冲突

问题一:当我们滑动RecyclerView组件时,上方的轮播图并没有进行滑动(NestedScrollView没有滑动,即滑动事件被RecyclerView消费了),当RecyclerView滑到底时,轮播图部分才进行滑动。 如下图,RecyclerView已经进行了滑动,但轮播图部分没有。

android recyclerview横向滑动 android scrollview recycleview_java


整体布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:AutoLoopStyle="http://schemas.android.com/apk/res-auto"
    android:id="@+id/home_pager_parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/color_page_Bg"
    android:gravity="center"
    android:orientation="vertical">


        <!--滚动页面-->
        <androidx.core.widget.NestedScrollView
            android:id="@+id/home_pager_nested_scroller"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:overScrollMode="never">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:orientation="vertical">

                <LinearLayout
                    android:id="@+id/home_pager_header_container"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"

                    android:orientation="vertical">

                    <RelativeLayout
                        android:layout_width="match_parent"
                        android:layout_height="125dp"
                        android:layout_marginBottom="14dp">

                        <com.example.taobaounion.ui.custom.AutoLoopViewPager
                            android:id="@+id/looper_pager"
                            android:layout_width="match_parent"
                            android:layout_height="match_parent"
                            AutoLoopStyle:duration="4000"
                            android:overScrollMode="never" />

                        <LinearLayout
                            android:id="@+id/looper_point_container"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:layout_alignParentBottom="true"
                            android:layout_marginBottom="10dp"
                            android:gravity="center"
                            android:orientation="horizontal" />

                    </RelativeLayout>
                    <!--标题-->
                    <include layout="@layout/include_home_pager_title_part"
                        />

                </LinearLayout>




                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/home_pager_content_list"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"

                    android:overScrollMode="never" />


            </LinearLayout>


        </androidx.core.widget.NestedScrollView>



</LinearLayout>

这并不符合我们的设计要求,我们希望让轮播图先滑到顶部,然后才进行RecycleView的滑动。在recycleView中有个方法使用recyclerView.setNestedScrollingEnabled(false); 即可解决滑动冲突。进行测试

@BindView(R.id.home_pager_content_list)
    public RecyclerView mContentList;
    @Override
    protected void initView(View rootView) {

        mContentList.setNestedScrollingEnabled(false);  //没错,就是我了
    }

测试后发现可行,但是出现了另一个问题,当轮播图部分滑动出屏幕时,就不能继续向上滑动了。 如下图,此时无论是NestedScrollView还是RecyclerView都不能再继续向上滑动了。

android recyclerview横向滑动 android scrollview recycleview_开发语言_02


基于这个现象,我猜测recyclerView.setNestedScrollingEnabled(false)方法实际上让NestedScrollView不再将滑动事件继续向下分发,而是独自消费了这个事件。那么由于NestScrollView的高度限制(轮播图的高度和recyclerView的item高度),以及RecyclerView没有收到滑动事件(不能继续更新item数据),因此此时不能再继续向上滑动。 如果我的猜测正确,那么要解决这个问题只需要在合适的时机让NestedScrollView将事件分发给RecyclerView,即在合适的时机调用mContentList.setNestedScrollingEnabled(true)即可。 于是我在HomePagerFragment类的initListener()方法中,为NestedScrollView设置了监听器,根据其滑动的距离为其设置是否独自消费事件

//尝试解决滑动冲突
    mNestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
        @Override
        public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
            LogUtils.e(HomePagerFragment.class,"NestedScrollView --> scrollY -->" + scrollY);
	    //滑动的距离大于或等于mContentList的顶部位置时
            if (scrollY >= mContentList.getTop()) mContentList.setNestedScrollingEnabled(true);
            else mContentList.setNestedScrollingEnabled(false);
        }
    });

测试结果:可行,滑动冲突解决,但是仍存在一些瑕疵。

2、解决刷新控件冲突

解决了滑动冲突之后,把刷新控件增加进来,这里1为了修改刷新组件的源代码,将它的模块依赖添加到项目中

项目地址:github地址

android recyclerview横向滑动 android scrollview recycleview_轮播图_03


android recyclerview横向滑动 android scrollview recycleview_Math_04

发现再次出现了问题。 如下图,还没拉到底呢,你怎么就给我加载更多了呢

android recyclerview横向滑动 android scrollview recycleview_轮播图_05


道理是一样的,因为刷新组件TwinklingRefreshLayout消耗了事件,RecyclerView并没有收到事件,所以出现了这种情况。 解决的方法就是在刷新组件消耗事件的方法中进行判断,如果RecyclerView还能进行滑动,那就不消耗这个事件,将事件分发给RecyclerView,否则就消耗这个事件,进行数据的加载。

通过观察源码发现RefreshProcessor类重写了dispatchTouchEvent方法,如下代码所示

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downEventSent = false;
                intercepted = false;
                mTouchX = ev.getX();
                mTouchY = ev.getY();

                if (cp.isEnableKeepIView()) {
                    if (!cp.isRefreshing()) {
                        cp.setPrepareFinishRefresh(false);
                    }
                    if (!cp.isLoadingMore()) {
                        cp.setPrepareFinishLoadMore(false);
                    }
                }

                cp.dispatchTouchEventSuper(ev);
                return true;
            case MotionEvent.ACTION_MOVE:
                mLastMoveEvent = ev;
                float dx = ev.getX() - mTouchX;
                float dy = ev.getY() - mTouchY;
                if (!intercepted && Math.abs(dx) <= Math.abs(dy) && Math.abs(dy) > cp.getTouchSlop()) {//滑动允许最大角度为45度
                    if (dy > 0 && ScrollingUtil.isViewToTop(cp.getTargetView(), cp.getTouchSlop()) && cp.allowPullDown()) {
                        cp.setStatePTD();
                        mTouchX = ev.getX();
                        mTouchY = ev.getY();
                        sendCancelEvent();
                        intercepted = true;
                        return true;
                    } else if (dy < 0 && ScrollingUtil.isViewToBottom(cp.getTargetView(), cp.getTouchSlop()) && cp.allowPullUp()) {
                        cp.setStatePBU();
                        mTouchX = ev.getX();
                        mTouchY = ev.getY();
                        intercepted = true;
                        sendCancelEvent();
                        return true;
                    }
                }
                if (intercepted) {
                    if (cp.isRefreshVisible() || cp.isLoadingVisible()) {
                        return cp.dispatchTouchEventSuper(ev);
                    }
                    if (!cp.isPrepareFinishRefresh() && cp.isStatePTD()) {
                        if (dy < -cp.getTouchSlop() || !ScrollingUtil.isViewToTop(cp.getTargetView(), cp.getTouchSlop())) {
                            cp.dispatchTouchEventSuper(ev);
                        }
                        dy = Math.min(cp.getMaxHeadHeight() * 2, dy);
                        dy = Math.max(0, dy);
                        cp.getAnimProcessor().scrollHeadByMove(dy);
                    } else if (!cp.isPrepareFinishLoadMore() && cp.isStatePBU()) {
                        //加载更多的动作
                        if (dy > cp.getTouchSlop() || !ScrollingUtil.isViewToBottom(cp.getTargetView(), cp.getTouchSlop())) {
                            cp.dispatchTouchEventSuper(ev);
                        }
                        dy = Math.max(-cp.getMaxBottomHeight() * 2, dy);
                        dy = Math.min(0, dy);
                        cp.getAnimProcessor().scrollBottomByMove(Math.abs(dy));
                    }
                    if (dy == 0 && !downEventSent) {
                        downEventSent = true;
                        sendDownEvent();
                    }
                    return true;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (intercepted) {
                    if (cp.isStatePTD()) {
                        willAnimHead = true;
                    } else if (cp.isStatePBU()) {
                        willAnimBottom = true;
                    }
                    intercepted = false;
                    return true;
                }
                break;
        }
        return cp.dispatchTouchEventSuper(ev);
    }

android recyclerview横向滑动 android scrollview recycleview_java_06


这里的isViewToBottom方法中的getTargetView就是拿到我们包裹在TwinklingRefreshLayout刷新控件里的内容

再看看isViewToBottom方法

public static boolean isViewToBottom(View view, int mTouchSlop) {
        if (view instanceof AbsListView) return isAbsListViewToBottom((AbsListView) view);
        if (view instanceof RecyclerView) return isRecyclerViewToBottom((RecyclerView) view);
        if (view instanceof WebView) return isWebViewToBottom((WebView) view, mTouchSlop);
        if (view instanceof ViewGroup) return isViewGroupToBottom((ViewGroup) view);
        return false;
    }

这个方法判断刷新组件中的子容器是什么类型,并根据类型调用不同的方法,我们添加一段判断子容器是NestedScrollView的语句,注意要写在判断ViewGroup类型的上面。

if (view instanceof NestedScrollView) return isNestedScrollViewToBottom((NestedScrollView)view);//这段是我添加的

根据之前的分析完成方法

private static boolean isNestedScrollViewToBottom(NestedScrollView view) {
        ViewGroup viewGroup = (ViewGroup) view.getChildAt(0); //根据布局文件知道这其实是一个LinearLayout
        RecyclerView recyclerView = null;
        //找到LinearLayout中的RecyclerView
        for (int i = 0; i < viewGroup.getChildCount(); i++) {
            if (viewGroup.getChildAt(i) instanceof RecyclerView)
                recyclerView = (RecyclerView) viewGroup.getChildAt(i);
        }
        //如果RecyclerView能继续向上滑动,则不消费这个事件
        //recyclerView.canScrollVertically(1))表示是否可以向上滑动
        if (recyclerView != null && recyclerView.canScrollVertically(1)) return false;
        //否则消费该事件
        return true;
    }

至此,刷新控件冲突就解决了。不过这个方法并没有经过细细的打磨,因此还存在着一些问题。比如最后实现的方法实际上并不完善,它只能在刷新组件中仅有一个RecyclerView的情况下才能正常使用。不过也是一种解决问题的思路