Android之解决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
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341