NestedScrollView内RecyclerView滚动监听失效,求可行实现方案
首先得点明问题的核心:NestedScrollView会拦截滚动事件,而且如果你的RecyclerView设置为wrap_content,它本身根本不会触发滚动(内容高度和自身高度一致),所以它的OnScrollListener自然收不到回调。下面给你几个可行的解决方案,按推荐程度排序:
1. 用ConcatAdapter替代NestedScrollView(最推荐)
如果你的页面是由多个列表+其他视图组成的,完全可以用AndroidX的ConcatAdapter把所有部分合并成一个单独的RecyclerView,彻底摆脱NestedScrollView的困扰,滚动监听也能正常工作,还能提升整体性能。
步骤很简单:
- 把非RecyclerView的视图(比如标题栏、操作按钮)包装成一个Adapter(比如写个继承自
RecyclerView.Adapter的HeaderAdapter,在onCreateViewHolder里返回你的视图) - 创建各个RecyclerView对应的业务Adapter
- 用
ConcatAdapter把这些Adapter按页面顺序合并 - 给合并后的RecyclerView设置滚动监听,和平时使用完全一致
代码示例:
// 包装头部视图的Adapter class HeaderAdapter extends RecyclerView.Adapter<HeaderAdapter.HeaderViewHolder> { private View headerView; public HeaderAdapter(View headerView) { this.headerView = headerView; } @NonNull @Override public HeaderViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new HeaderViewHolder(headerView); } @Override public void onBindViewHolder(@NonNull HeaderViewHolder holder, int position) {} @Override public int getItemCount() { return 1; } static class HeaderViewHolder extends RecyclerView.ViewHolder { public HeaderViewHolder(@NonNull View itemView) { super(itemView); } } } // 合并Adapter并设置到主RecyclerView // 假设你有两个业务Adapter:adapter1、adapter2,还有一个头部视图headerView HeaderAdapter headerAdapter = new HeaderAdapter(headerView); ConcatAdapter concatAdapter = new ConcatAdapter(headerAdapter, adapter1, adapter2); mainRecyclerView.setAdapter(concatAdapter); // 设置滚动监听 mainRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); // 在这里处理滚动逻辑,比如判断是否到了底部 LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); int lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition(); int totalItemCount = layoutManager.getItemCount(); if (lastVisibleItem == totalItemCount - 1 && dy > 0) { // 滚动到底部,执行加载更多等操作 } } });
2. 给RecyclerView设置固定高度,保留NestedScrollView
如果一定要保留NestedScrollView,那你需要给目标RecyclerView设置固定高度(或者用RelativeLayout的布局约束让它有确定的高度,比如限制它的顶部到上一个视图、底部到下一个视图)。这样RecyclerView内部的内容会独立滚动,它的OnScrollListener就能正常收到回调。
这种方案适合你的两个RecyclerView需要各自独立滚动的场景,NestedScrollView负责整个页面的滚动,RecyclerView负责自身内容的滚动。
3. 监听NestedScrollView的滚动,模拟RecyclerView的滚动逻辑
如果你的RecyclerView必须是wrap_content(和其他视图一起在NestedScrollView里整体滚动),那可以通过监听NestedScrollView的滚动事件,结合RecyclerView的LayoutManager来判断滚动状态,模拟出RecyclerView滚动监听的效果。
代码示例:
nestedScrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() { @Override public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { LinearLayoutManager layoutManager = (LinearLayoutManager) targetRecyclerView.getLayoutManager(); if (layoutManager == null) return; // 获取RecyclerView的可见项信息 int firstVisiblePos = layoutManager.findFirstVisibleItemPosition(); int lastVisiblePos = layoutManager.findLastVisibleItemPosition(); int totalItemCount = layoutManager.getItemCount(); // 判断是否滚动到RecyclerView的底部 View lastChild = targetRecyclerView.getChildAt(targetRecyclerView.getChildCount() - 1); boolean isBottom = lastVisiblePos == totalItemCount - 1 && lastChild.getBottom() <= targetRecyclerView.getHeight(); if (isBottom) { // 执行你原本在RecyclerView滚动到底部时的逻辑 } // 同理也可以判断是否滚动到顶部 boolean isTop = firstVisiblePos == 0 && targetRecyclerView.getChildAt(0).getTop() >= 0; } });
4. 自定义RecyclerView,强制传递滚动事件
如果上面的方法都不适用,你可以自定义一个RecyclerView,重写onScrollChanged方法,手动触发滚动监听的回调,这样即使在NestedScrollView里,只要RecyclerView的内容有滚动(比如设置了固定高度),就能收到监听。
代码示例:
public class ObservableRecyclerView extends RecyclerView { private OnScrollListener mScrollListener; public ObservableRecyclerView(Context context) { super(context); } public ObservableRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); } public ObservableRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); if (mScrollListener != null) { // 触发滚动偏移的回调 mScrollListener.onScrolled(this, l - oldl, t - oldt); // 触发滚动状态变化的回调 int scrollState = getScrollState(); mScrollListener.onScrollStateChanged(this, scrollState); } } @Override public void addOnScrollListener(@NonNull OnScrollListener listener) { super.addOnScrollListener(listener); mScrollListener = listener; } }
然后在布局里使用这个自定义RecyclerView,设置正常的OnScrollListener即可。
内容的提问来源于stack exchange,提问作者AAA

