文章目录
1.SnapHelper 的应用情景
通常我们使用RecyclerView来实现简单图片轮播图Banner时,需要实现按图片翻页效果,但RecyclerView会在滚动过程中是“过程停留”无法达到“翻页”效果,这时候我们就不得不借助SnapHelper类来使得RecyclerView具备类似ViewPager“翻页”效果的能力。但随着页面UI布局的复杂性,有时候我们需要嵌套RecyclerView并结合SnapHelper。
2.问题现象
多层RecyclerView嵌套时使用SnapHelper工具类配合,向下滚动Item列表正常,但向上滚动会立即强退并杀死app程序。报错问题:“java.lang.IllegalStateException: An instance of OnFlingListener already set.”
3.分析原因
首先来了解一个概念,手指在屏幕上滑动RecyclerView然后松手,RecyclerView中的内容会顺着惯性继续往手指滑动的方向继续滚动直到停止,这个过程叫做Fling。Fling操作从手指离开屏幕瞬间被触发,在滚动停止时结束。而OnFlingListener显然就是监听Fling滚动事件的监听器。
4.原因重点:(SnapHelper被多次创建并绑定到同一个RecyclerView)
通常我们在做RecyclerView的嵌套时总会遇到这样的问题,是因为每次在onBindViewHolder中都这样写:
SnapHelper snapHelper = new PagerSnapHelper()
snapHelper.attachToRecyclerView(recyclerView)
每次滑动RecyclerView都需要重新创建SnapHelper对象并将其附着到RecyclerView上,导致一个RecyclerIView会绑定多个SnapHelper,在回头绘制RecyclerView时,会发现一个RecyclerView的SnapHelper实例(多个)重复设置,导致滚动事件出问题而退出滚动,致使整个app应用崩溃退出!
5.解决方法
5.1第一种方法:
在重新绘制RecyclerView时首先移除创建的前一个SnapHelper实例的OnFlingListener监听器。
Tips:也就是RecyclerView在第二次滑动到该位置时
Java语言
SnapHelper snapHelper = new PagerSnapHelper()
recycleView.setOnFlingListener(null)
snapHelper.attachToRecyclerView(recyclerView)
Kotlin语言
val snapHelper: SnapHelper = PagerSnapHelper()
recycleView.onFlingListener = null
snapHelper.attachToRecyclerView(recyclerView)
Tips:SnapHelper通过attachToRecyclerView()方法附着到RecyclerView上,从而实现辅助RecyclerView滚动对齐操作。
5.2第二种方法:
将SnapHelper snapHelper = new
PagerSnapHelper()放在全局定义(针对类),允许类中只存在一个SnapHelper对象。每次重新绘制RecyclerView时总是调用该SnapHelper实例对象的onFlingListener。Tips:此方法不用添加任何代码,仅需要将SnapHelper snapHelper = new
PagerSnapHelper()放在与重写方法onBindViewHolder()同级的位置。
6原理剖析
据下面的源码可以看到当与RecyclerView绑定的SnapHelper实例对象的OnFlingListener已经被设置时,再次设置系统会抛出异常:”An instance of OnFlingListener already set.“
7源码解析:
错误类型&具体错误:IllegalArgumentException:An instance of OnFlingListener already set.
/**
* Attaches the {@link SnapHelper} to the provided RecyclerView, by calling
* {@link RecyclerView#setOnFlingListener(RecyclerView.OnFlingListener)}.
* You can call this method with {@code null} to detach it from the current RecyclerView.
*
* @param recyclerView The RecyclerView instance to which you want to add this helper or
* {@code null} if you want to remove SnapHelper from the current
* RecyclerView.
*
* @throws IllegalArgumentException if there is already a {@link RecyclerView.OnFlingListener}
* attached to the provided {@link RecyclerView}.
*
*/
/**
* Called when an instance of a {@link RecyclerView} is attached.
*/
private void setupCallbacks() throws IllegalStateException {
if (mRecyclerView.getOnFlingListener() != null) {
throw new IllegalStateException("An instance of OnFlingListener already set.");
}
mRecyclerView.addOnScrollListener(mScrollListener);
mRecyclerView.setOnFlingListener(this);
}