如何在RecyclerView.OnScrollListener中区分用户滚动与scrollToPosition()滚动?
区分RecyclerView滚动来源:用户触摸 vs 代码调用
碰到这个问题太常见了,毕竟RecyclerView的OnScrollListener确实没直接提供区分滚动来源的参数。我给你几个实用的方案,都是实际项目里验证过的:
方案1:用标志位手动追踪代码触发的滚动
这是最直接的方案,核心就是用一个布尔变量标记当前滚动是否是代码发起的:
- 首先在你的Activity/Fragment或者逻辑类里定义一个变量:
private boolean isProgrammaticScroll = false; - 当你通过代码调用滚动方法时,先把标志设为
true:// 调用scrollToPosition时 isProgrammaticScroll = true; mainRecyclerView.scrollToPosition(targetItemPosition); // 或者调用smoothScrollToPosition时 isProgrammaticScroll = true; mainRecyclerView.smoothScrollToPosition(targetItemPosition); - 然后在
OnScrollListener的回调里判断这个标志:mainRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if (isProgrammaticScroll) { // 处理代码触发的滚动逻辑 Log.d("Scroll", "这是代码调用的滚动"); } else { // 处理用户触摸触发的滚动逻辑 Log.d("Scroll", "这是用户触摸的滚动"); } } @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); // 滚动停止后重置标志,避免影响后续滚动 if (newState == RecyclerView.SCROLL_STATE_IDLE) { isProgrammaticScroll = false; } } });
注意:如果用带动画的滚动(比如
smoothScrollToPosition),不要在onScrolled()里直接重置标志——因为动画会触发多次onScrolled,等到滚动状态变为SCROLL_STATE_IDLE再重置才准确。
方案2:自定义RecyclerView封装滚动追踪
如果你的项目里多个地方都需要区分滚动来源,自定义RecyclerView会更优雅,把追踪逻辑封装在内部:
- 自定义一个
TrackingRecyclerView:public class TrackingRecyclerView extends RecyclerView { private boolean isProgrammaticScroll = false; public TrackingRecyclerView(@NonNull Context context) { super(context); } public TrackingRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public TrackingRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } // 重写所有触发滚动的公开方法 @Override public void scrollToPosition(int position) { isProgrammaticScroll = true; super.scrollToPosition(position); } @Override public void smoothScrollToPosition(int position) { isProgrammaticScroll = true; super.smoothScrollToPosition(position); } @Override public void scrollBy(int x, int y) { isProgrammaticScroll = true; super.scrollBy(x, y); } // 对外提供判断方法 public boolean isProgrammaticScroll() { return isProgrammaticScroll; } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); // 滚动停止后自动重置标志 if (isProgrammaticScroll && getScrollState() == SCROLL_STATE_IDLE) { isProgrammaticScroll = false; } } } - 然后在布局里用这个自定义RecyclerView,在监听器里直接判断:
trackingRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); TrackingRecyclerView tr = (TrackingRecyclerView) recyclerView; if (tr.isProgrammaticScroll()) { // 代码触发的滚动 } else { // 用户触摸的滚动 } } });
方案3:通过滚动状态间接判断
如果不想用标志位,可以利用RecyclerView的滚动状态变化来间接区分:
- 用户触摸滚动时,必然会经历
SCROLL_STATE_TOUCH_SCROLL状态(手指按住滑动),而代码触发的滚动不会进入这个状态。我们可以基于这点做判断:private boolean isUserInitiatedScroll = false; mainRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == RecyclerView.SCROLL_STATE_TOUCH_SCROLL) { // 用户开始触摸滑动 isUserInitiatedScroll = true; } else if (newState == RecyclerView.SCROLL_STATE_IDLE) { // 滚动停止,重置标记 isUserInitiatedScroll = false; } } @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if (isUserInitiatedScroll) { // 这是用户发起的滚动(包括触摸滑动和惯性滑动) } else { // 这是代码发起的滚动 } } });
注意:这个方案会把用户触发的惯性滚动也归为“用户发起”,如果你的场景需要区分“触摸中”和“惯性滑动”,可能需要调整,但如果只是区分“用户发起”和“代码发起”,完全够用。
内容的提问来源于stack exchange,提问作者CommonsWare
相关产品推荐
相关产品推荐

