NestedScrollView嵌套RecyclerView滚动异常及复用失效问题排查
核心问题定位
当NestedScrollView上方存在水平RecyclerView时,触发了嵌套滚动事件冲突,同时自定义ScrollView的测量逻辑干扰了垂直RecyclerView的高度计算——RecyclerView被强制测量为所有条目的总高度,既阻塞了父布局的滚动,又让RecyclerView失去了View复用的基础(直接显示全部条目,无需滚动加载)。
1. 恢复RecyclerView的View复用与父布局滚动
给嵌套在ScrollView里的垂直RecyclerView添加以下属性:
android:nestedScrollingEnabled="false"
这会让父ScrollView接管所有垂直滚动事件,同时让RecyclerView的高度被限制在父容器的可用空间内,不再强制展开为全部条目高度,自然恢复View复用机制。
2. 修正自定义ScrollViewWithMaxHeight的测量逻辑
检查你的ScrollViewWithMaxHeight的onMeasure方法,确保它正确限制最大高度,同时不强制子View的高度。示例正确实现:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int screenHeight = getResources().getDisplayMetrics().heightPixels; int maxAllowedHeight = (int) (screenHeight * 0.4f); // 用AT_MOST模式限制高度,允许子View自适应但不超过最大值 heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxAllowedHeight, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, heightMeasureSpec); }
如果之前的实现错误地将子View高度强制设为maxHeight,会导致垂直RecyclerView被拉伸至全部条目高度,直接触发复用失效。
3. 解决水平RecyclerView的触摸事件冲突
水平RecyclerView可能会拦截垂直滑动事件,导致父ScrollView无法滚动。给水平RecyclerView添加触摸监听,区分水平/垂直滑动:
horizontalRecyclerView.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_MOVE) { float deltaX = Math.abs(event.getX() - v.getTag(R.id.last_touch_x, 0f)); float deltaY = Math.abs(event.getY() - v.getTag(R.id.last_touch_y, 0f)); if (deltaY > deltaX) { // 垂直滑动,让父布局处理事件 v.getParent().requestDisallowInterceptTouchEvent(false); } else { // 水平滑动,自己处理 v.getParent().requestDisallowInterceptTouchEvent(true); } v.setTag(R.id.last_touch_x, event.getX()); v.setTag(R.id.last_touch_y, event.getY()); } return false; });
(注:需要在res/values/ids.xml中定义last_touch_x和last_touch_y两个id,或用其他方式记录触摸坐标)
4. 替代方案:用普通ScrollView替代NestedScrollView
如果NestedScrollView的嵌套滚动逻辑过于复杂,直接用自定义的ScrollViewWithMaxHeight(继承自普通ScrollView)包裹整个布局,水平RecyclerView和垂直RecyclerView都放在里面,同时给垂直RecyclerView设置nestedScrollingEnabled="false",这种方式更简单,能避免大部分嵌套滚动冲突。
内容的提问来源于stack exchange,提问作者Martin

