本文对应【领券联盟】系列视频第44~48P。方法可能存有缺陷,主要希望为各位提供另一种思路。
1、解决RecyclerView与NestedScrollView的滑动冲突
没做任何修改的情况下,当我们滑动RecyclerView组件时,上方的轮播图并没有进行滑动(NestedScrollView没有滑动,即滑动事件被RecyclerView消费了),当RecyclerView滑到底时,轮播图部分才进行滑动。 如下图,RecyclerView已经进行了滑动,但轮播图部分没有。(PS:手机录屏转成gif似乎太大了,只能将就看咯)
但这不符合我们的设计要求,我们希望能让轮播图部分先滑到顶部,然后才进行RecyclerView的滑动。 我在B站的评论区看到有同学回复说:使用recyclerView.setNestedScrollingEnabled(false); 即可解决滑动冲突。 马上就进行测试,毕竟能偷懒,能只写一行代码就坚决不写两行。 在HomePagerFragment类的initView()方法中添加此方法的调用:
@Override
protected void initView(View rootView) {
//省略部分代码
mHomePageContentAdapter = new HomePageContentAdapter();
mContentList.setAdapter(mHomePageContentAdapter);
mContentList.setNestedScrollingEnabled(false); //没错,就是我了
//设置刷新
// twinklingRefreshLayout.setEnableRefresh(false);
// twinklingRefreshLayout.setEnableLoadmore(true);
}
测试后发现可行,但是出现了另一个问题,当轮播图部分滑动出屏幕时,就不能继续向上滑动了。 如下图,此时无论是NestedScrollView还是RecyclerView都不能再继续向上滑动了。
基于这个现象,我猜测recyclerView.setNestedScrollingEnabled(false)方法实际上让NestedScrollView不再将滑动事件继续向下分发,而是独自消费了这个事件。那么由于NestScrollView的高度限制,以及RecyclerView没有收到滑动事件,因此此时不能再继续向上滑动。 如果我的猜测正确,那么要解决这个问题只需要在合适的时机让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、解决刷新控件冲突
解决了滑动冲突之后,把刷新组件的注释去掉,再度测试,发现再次出现了问题。 如下图,还没拉到底呢,你怎么就给我加载更多了呢
道理是一样的,因为刷新组件消耗了事件,RecyclerView并没有收到事件,所以出现了这种情况。 解决的方法就是在刷新组件消耗事件的方法中进行判断,如果RecyclerView还能进行滑动,那就不消耗这个事件,将事件分发给RecyclerView,否则就消耗这个事件,进行数据的加载。 由于我没有像老师的视频中一样重写了一个TbNestedScrollView去继承NestedScrollView,所以处理的方法有所不同,但是思路是相同的。 1. 为了修改刷新组件的源代码,将它的模块依赖添加到项目中。这一部分老师在视频中有细讲,就不赘述了。 2. 找到消费事件的方法。跟着老师走,就能找到这个方法。这个方法判断刷新组件中的子容器是什么类型,并根据类型调用不同的方法,我们添加一段判断子容器是NestedScrollView的语句,注意要写在判断ViewGroup类型的上面。
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 NestedScrollView) return isNestedScrollViewToBottom((NestedScrollView)view);//这段是我添加的
if (view instanceof ViewGroup) return isViewGroupToBottom((ViewGroup) view);
return false;
}
- 根据之前的分析完成方法
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能继续向上滑动,则不消费这个事件
if (recyclerView != null && recyclerView.canScrollVertically(1)) return false;
return true;
}
至此,刷新控件冲突就解决了。
3、结语
本文纯粹是因为想偷懒而诞生的,想尝试更简单的方式解决冲突问题,结果误打误撞搞定了。不过这个方法并没有经过细细的打磨,因此还存在着一些问题。比如最后实现的方法实际上并不完善,它只能在刷新组件中仅有一个RecyclerView的情况下才能正常使用,治标不治本。不过,搞定这个问题并整理成文章已经十分耗费精力了,只希望这篇文章能帮到各位同学,提供一些解决问题的思路,共同思考,共同进步。