总所周知,我们一般用LayoutManage的边检检测方法来确定,是否触底或者触顶,如下:
!recyclerView.canScrollVertically(-1):检测是否到达顶部。
!recyclerView.canScrollVertically(1):检测是否到达底部。
但是问题来了,当你手指松开后如果RecycleView滚动可能发生了超出内容范围(例如过滚动或弹性滚动),那么这时调用canScrollVertically(xxx)得到值并不准确。
另外,我们可能还会用各种自定义计算的方式来检测是否触底或者触顶,比如,滑动后检测第一条数据的顶部是否触达了页面里上一个view的底部附近,或者检测最后一条数据是否到触达了屏幕的高度,但是很不幸,因为RecycleView的回收机制已经各种case下,无论是val view = LayoutManage.findFirstVisibleItemPosition()还是val view = RecycleView.findItemViewByPosition(),总有一个时刻,view是null,其实无法时刻精准。
经过多种方案的尝试改进方案,只有该方案比较精准:
结合 RecyclerView.computeVerticalScrollOffset 和滚动状态
使用 computeVerticalScrollOffset 获取滚动的精确偏移量,并结合 RecyclerView.OnScrollListener 检测滚动状态,确保能准确检测到触摸松开后RecyclerView 的惯性滚动状态和实时的偏移值的一系列行为是否到达边界。
computeVerticalScrollOffset:
直接返回 RecyclerView 的当前滚动偏移量,无需累积。
值始终精确反映当前滚动的位置,不会受边界条件的影响
//如果是检测是否触底的话那么需要删除@Check_2的部分,采用@Check_1(因为触底检测必须在SCROLL_STATE_IDLE状态下),
//反之如果是要检测Item数量是否已超出屏幕底部,那么删除@Check_1的部分,采用@Check_2,否则不够纵向丝滑。
private var scrollingDirection: ScrollingDirection = ScrollingDirection.NONE
private enum class ScrollingDirection {
SCROLLED_DOWN, SCROLLED_UP, NONE // We don't care about horizontal scroll for now
}
recyclerView?.setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_MOVE -> {
if (previousY <= 0f) {
previousY = event.y
}
val deltaY = event.y - previousY
if (deltaY > 0) {
scrollingDirection = ScrollingDirection.SCROLLED_UP
Log.d("logTag","ACTION_MOVE down: deltaY=$deltaY")
} else if (deltaY < 0) {
scrollingDirection = ScrollingDirection.SCROLLED_DOWN
Log.d("logTag","ACTION_MOVE up: deltaY=$deltaY")
}
previousY = event.y
}
//@Check_1-----start
MotionEvent.ACTION_UP,MotionEvent.ACTION_CANCEL -> {
//Replace with Your true condition.
//Maybe 'LayoutManage.findLastVisibleItemPosition()' used here
//to generate condition of 'shouldCollapse'.
val shouldCollapse = true
if (shouldCollapse) {
//collapseIfNeeded()
}
}
//@Check_1-----end
}
false
}
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
// Detect state of stopping scrolling.
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
val offset = recyclerView.computeVerticalScrollOffset()
val extent = recyclerView.computeVerticalScrollExtent()
val range = recyclerView.computeVerticalScrollRange()
if (offset == 0) {
println("Reached the top")
//expandIfNeeded()
}
//@Check_2-----start
else if (offset + extent == range) {
println("Reached the bottom")
//collapseIfNeeded()
}
//@Check_2-----end
//reset value.
previousY = 0f
scrollingDirection = ScrollingDirection.NONE
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
// Print scrolling offset on time
val currentOffset = recyclerView.computeVerticalScrollOffset()
println("Current Scroll Offset: $currentOffset")
}
})
拓展:
1.当RecycleView已经触底/触顶时,这时继续向上/向下滑动时,onScrolled()方法不会被调用.
2.findLastVisibleItemPosition()方法还比较准,不是’findLastCompleteVisibleItemPosition()‘这个精确度似乎差点意思
-----------------------------End-----------------------------