You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 04:18:01