Reputation: 1007265
In my onScrolled()
method of a RecyclerView.OnScrollListener
, I need to be able to distinguish between two different sources of scroll events:
RecyclerView
using touch eventsRecyclerView
using scrollToPosition()
There is no parameter to onScrolled()
that would appear to cover this. Is there a recipe for making this distinction?
FWIW, my scenario: I have two RecyclerView
widgets. One is the master, and shows a traditional vertical list. The other is serving as a ViewPager
replacement, to allow for swiping through details of the same items that are in the vertical list. In a large-screen environment, both RecyclerView
widgets will be visible at once (master-detail pattern), and I need to keep them synchronized. If the user swipes the pager RecyclerView
, I need to change an indicator on the list RecyclerView
rows to match. If the user taps on a row in the list RecyclerView
, I need to update the current page in the pager RecyclerView
. To listen for swipe events on the pager, I am using OnScrollListener
, but that also is getting triggered when I scroll the pager in response to list row clicks.
Upvotes: 5
Views: 866
Reputation: 656
The accepted answer will not work if you are using smoothScrollBy()
or smoothScrollToPosition()
. The only way to tell if the scroll was from user is to set a flag when the user is dragging, when this happen the RecyclerView
state would be changed to RecyclerView.SCROLL_STATE_DRAGGING
. Then, when the state changed to RecyclerView.SCROLL_STATE_IDLE
, you need to clear the flag.
var scrolledByUser = false
rv.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(
recyclerView: RecyclerView,
newState: Int
) {
when (newState) {
RecyclerView.SCROLL_STATE_DRAGGING -> {
scrolledByUser = true
}
RecyclerView.SCROLL_STATE_IDLE -> {
if (scrolledByUser) {
// fromUser
} else {
// from smoothScroll
}
scrolledByUser = false
}
}
}
})
Upvotes: 1
Reputation: 1007265
Gabe Sechan's suggestion of using the scroll-state change is a good one.
Unfortunately, there is no listener for the scroll state — it requires that you subclass RecyclerView
. So, I whipped up a RecyclerViewEx
to add a listener API for that event:
public class RecyclerViewEx extends RecyclerView {
interface OnScrollStateChangedListener {
void onScrollStateChanged(int state);
}
final private ArrayList<OnScrollStateChangedListener> listeners=new ArrayList<>();
public RecyclerViewEx(Context context) {
super(context);
}
public RecyclerViewEx(Context context, @Nullable
AttributeSet attrs) {
super(context, attrs);
}
public RecyclerViewEx(Context context, @Nullable AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public void addOnScrollStateChangedListener(OnScrollStateChangedListener listener) {
listeners.add(listener);
}
public void removeOnScrollStateChangedListener(OnScrollStateChangedListener listener) {
listeners.remove(listener);
}
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
for (OnScrollStateChangedListener listener : listeners) {
listener.onScrollStateChanged(state);
}
}
}
scrollToPosition()
does not trigger a scroll-state change, whereas user swipes do. So, I can distinguish between the two scenarios that way.
rve.addOnScrollStateChangedListener(
scrollState -> {
if (scrollState==RecyclerView.SCROLL_STATE_IDLE) {
// do something cool, now that the user swipe is complete
}
});
Given this, I do not need a flag or any other state, which helps keep the logic simpler.
Upvotes: 3