Reputation: 22183
I need to detect the start/end and direction of scroll in a recyclerview. The scroll listener has two methods: onScrolled()
and onScrollStateChanged()
. The first method is called after the scroll is started (indeed is called onScrolled() and not onScrolling()). The second method gives information about the state but I don't have the direction information. How can I achieve my goal?
Upvotes: 70
Views: 96082
Reputation: 1971
Other answers where not working for me, they either do something else or not working perfectly. this works
binding.filters.addOnScrollListener(object : OnScrollListener() {
private var layoutManager: LinearLayoutManager? = null
var totalItemCount = 0
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (layoutManager == null) {
layoutManager = recyclerView.layoutManager as LinearLayoutManager
totalItemCount = layoutManager?.itemCount ?: 0
}
if(layoutManager?.findFirstCompletelyVisibleItemPosition() == 0) //reached start
if(layoutManager?.findLastCompletelyVisibleItemPosition() == totalItemCount - 1) //reached end
}
})
Upvotes: 0
Reputation: 1244
You may use RecyclerView.getScrollState()
for detect RecyclerView.SCROLL_STATE_IDLE
or another one state.
My case:
if (recyclerView?.scrollState != RecyclerView.SCROLL_STATE_IDLE) {
return
}
Upvotes: 0
Reputation: 1575
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (!recyclerView.canScrollVertically(1) && newState == RecyclerView.SCROLL_STATE_IDLE) {
//reached end
}
if (!recyclerView.canScrollVertically(-1) && newState == RecyclerView.SCROLL_STATE_IDLE) {
//reached top
}
if(newState == RecyclerView.SCROLL_STATE_DRAGGING){
//scrolling
}
}
Upvotes: 4
Reputation: 1730
Use this code for avoiding repeated calls
use -1 for detecting top
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (!recyclerView.canScrollVertically(1) && newState==RecyclerView.SCROLL_STATE_IDLE) {
Log.d("-----","end");
}
}
});
Upvotes: 9
Reputation: 865
I think the other answers do not touch your pain point.
About the question
As you said, RecycleView will invoke onScrollStateChanged() before onScrolled() method. However, onScrollStateChanged() method can only tell you the RecycleView's three status:
That means, assume the RecycleView is on its top now, if the user scrolls up, it can only trigger onScrollStateChanged()
method, but not onScrolled()
method.
And if user scrolls down, it can trigger onScrollStateChanged()
method firstly, then onScrolled()
method.
Then the problem comes, SCROLL_STATE_DRAGGING
can only tell you the user is dragging view, not tell the direction of his dragging.
You have to combine with the dy
from onScrolled()
method to detect the dragging direction, but onScrolled()
not triggered with onScrollStateChanged()
.
MySolution:
Record scroll state when onScrollStateChanged()
triggered, then deley a time, like 10ms, check if have a Y-axis movement, and make a conclusion of dragging direction.
Kotlin Code:
class DraggingDirectionScrollListener(private val view: View)
: RecyclerView.OnScrollListener() {
private val DIRECTION_NONE = -1
private val DIRECTION_UP = 0
private val DIRECTION_DOWN = 1
var scrollDirection = DIRECTION_NONE
var listStatus = RecyclerView.SCROLL_STATE_IDLE
var totalDy = 0
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
listStatus = newState
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
scrollDirection = DIRECTION_NONE
}
if (isOnTop() && newState == RecyclerView.SCROLL_STATE_DRAGGING) {
view.postDelayed({
if (getDragDirection() == DIRECTION_DOWN){
// Drag down from top
}else if (getDragDirection() == DIRECTION_UP) {
// Drag Up from top
}
}, 10)
}
}
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
this.totalDy += dy
scrollDirection = when {
dy > 0 -> DIRECTION_UP
dy < 0 -> DIRECTION_DOWN
else -> DIRECTION_NONE
}
}
/**
* is on the top of the list
*/
private fun isOnTop():Boolean{
return totalDy == 0
}
/**
* detect dragging direction
*/
private fun getDragDirection():Int {
if (listStatus != RecyclerView.SCROLL_STATE_DRAGGING) {
return DIRECTION_NONE
}
return when (scrollDirection) {
DIRECTION_NONE -> if (totalDy == 0){
DIRECTION_DOWN // drag down from top
}else{
DIRECTION_UP // drag up from bottom
}
DIRECTION_UP -> DIRECTION_UP
DIRECTION_DOWN -> DIRECTION_DOWN
else -> DIRECTION_NONE
}
}
}
Upvotes: 0
Reputation: 1477
here is complete solution to make an action after scroll is stopped (for RecyclerView)
that has corrected my Exception OutOfBounds related to the recyclerView scrolling
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
//Your action here
}
});
Upvotes: -2
Reputation: 2494
step 1 You can create a class extending RecyclerView.OnScrollListener and override these methods
public class CustomScrollListener extends RecyclerView.OnScrollListener {
public CustomScrollListener() {
}
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
switch (newState) {
case RecyclerView.SCROLL_STATE_IDLE:
System.out.println("The RecyclerView is not scrolling");
break;
case RecyclerView.SCROLL_STATE_DRAGGING:
System.out.println("Scrolling now");
break;
case RecyclerView.SCROLL_STATE_SETTLING:
System.out.println("Scroll Settling");
break;
}
}
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dx > 0) {
System.out.println("Scrolled Right");
} else if (dx < 0) {
System.out.println("Scrolled Left");
} else {
System.out.println("No Horizontal Scrolled");
}
if (dy > 0) {
System.out.println("Scrolled Downwards");
} else if (dy < 0) {
System.out.println("Scrolled Upwards");
} else {
System.out.println("No Vertical Scrolled");
}
}
}
step 2- Since setOnScrollListener is deprecated It is better to use addOnScrollListener
mRecyclerView.addOnScrollListener(new CustomScrollListener());
Upvotes: 137
Reputation: 1533
Easiest way to do it is to pull out your layoutManager. For example
private RecyclerView.OnScrollListener mListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
GridLayoutManager layoutManager=GridLayoutManager.class.cast(recyclerView.getLayoutManager());
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
int pastVisibleItems = layoutManager.findFirstCompletelyVisibleItemPosition();
if(pastVisibleItems+visibleItemCount >= totalItemCount){
// End of the list is here.
Log.i(TAG, "End of list");
}
}
}
Hope it helps!
Upvotes: 4
Reputation: 16910
See the documentation for onScrollStateChanged(int state)
.
The three possible values are:
SCROLL_STATE_IDLE
: No scrolling is done.SCROLL_STATE_DRAGGING
: The user is dragging his finger on the screen (or it is being done programatically.SCROLL_STATE_SETTLING
: User has lifted his finger, and the animation is now slowing down.So if you want to detect when the scrolling is starting and ending, then you can create something like this:
public void onScrollStateChanged(int state) {
boolean hasStarted = state == SCROLL_STATE_DRAGGING;
boolean hasEnded = state == SCROLL_STATE_IDLE;
}
Upvotes: 54