返回顶部
首页 > 资讯 > 移动开发 >Android之解决RecyclerView与NestedScrollView的滑动冲突方法
  • 886
分享到

Android之解决RecyclerView与NestedScrollView的滑动冲突方法

androidjava开发语言 2023-09-15 11:09:51 886人浏览 安东尼
摘要

1、解决RecyclerView与NestedScrollView的滑动冲突 问题一:当我们滑动RecyclerView组件时,上方的轮播图并没有进行滑动(NestedScrollView没有滑动,即

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

问题一:当我们滑动RecyclerView组件时,上方的轮播图并没有进行滑动(NestedScrollView没有滑动,即滑动事件被RecyclerView消费了),当RecyclerView滑到底时,轮播图部分才进行滑动。 如下图,RecyclerView已经进行了滑动,但轮播图部分没有。
在这里插入图片描述
整体布局

<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.AutoLoopViewPagerandroid:id="@+id/looper_pager"android:layout_width="match_parent"android:layout_height="match_parent"AutoLoopStyle:duration="4000"android:overScrollMode="never" />                        <LinearLayoutandroid: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都不能再继续向上滑动了。
在这里插入图片描述
基于这个现象,我猜测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地址
在这里插入图片描述
在这里插入图片描述

发现再次出现了问题。 如下图,还没拉到底呢,你怎么就给我加载更多了呢
在这里插入图片描述
道理是一样的,因为刷新组件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);    }

在这里插入图片描述
这里的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的情况下才能正常使用。不过也是一种解决问题的思路

来源地址:https://blog.csdn.net/ChenYiRan123456/article/details/130973175

--结束END--

本文标题: Android之解决RecyclerView与NestedScrollView的滑动冲突方法

本文链接: https://lsjlt.com/news/408455.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作