Android垂直嵌套RecyclerView(父列表分页)内层无法滚动如何解决
Android同方向垂直嵌套RecyclerView滚动冲突解决方案
同方向垂直嵌套的RecyclerView出现内层无法响应滚动,核心原因是默认滑动事件拦截逻辑优先将事件分配给外层,且wrap_content高度的内层RecyclerView使用默认LayoutManager时无法正确处理滚动分发,网上常见的直接关闭嵌套滚动的方案会导致外层分页加载失效、滑动惯性丢失,按以下步骤实现即可保证两层滚动正常、外层分页逻辑不受影响:
1 基础配置修正
- 外层
rv_sales_report保持原有配置和分页加载逻辑不变,无需修改XML属性 - 内层
rv_dynamic_inner_reporting移除XML中默认配置的app:layoutManager属性,后续代码中设置自定义LayoutManager - 不要给任意一层RecyclerView添加
android:nestedScrollingEnabled="false"属性,避免破坏滑动连贯性和分页监听
自定义内层专用LayoutManager
新建InnerVerticalLinearLayoutManager类,解决wrap_content高度下的测量问题、关闭内层默认的滚动消费逻辑,避免事件抢用:
class InnerVerticalLinearLayoutManager(context: Context) : LinearLayoutManager(context) { override fun canScrollVertically(): Boolean { return false } override fun onMeasure( recycler: RecyclerView.Recycler, state: RecyclerView.State, widthSpec: Int, heightSpec: Int ) { var totalHeight = 0 for (i in 0 until itemCount) { val childView = recycler.getViewForPosition(i) addView(childView) measureChildWithMargins(childView, widthSpec, 0, heightSpec, 0) val layoutParams = childView.layoutParams as RecyclerView.LayoutParams totalHeight += childView.measuredHeight + layoutParams.topMargin + layoutParams.bottomMargin recycler.recycleView(childView) } setMeasuredDimension( View.MeasureSpec.getSize(widthSpec), totalHeight + paddingTop + paddingBottom ) } }
代码中给内层RecyclerView初始化该LayoutManager:
rv_dynamic_inner_reporting.layoutManager = InnerVerticalLinearLayoutManager(context)
2 滑动事件分发处理
给外层RecyclerView添加触摸监听,自动判断滑动时应该由内层还是外层消费事件,保证内层滚到边界后事件自动交还给外层,不影响分页触发:
rv_sales_report.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener { private var touchStartY = 0f private var targetInnerRv: RecyclerView? = null override fun onInterceptTouchEvent(outerRv: RecyclerView, event: MotionEvent): Boolean { when (event.action) { MotionEvent.ACTION_DOWN -> { touchStartY = event.y // 查找触摸位置对应的内层RecyclerView val touchedItem = outerRv.findChildViewUnder(event.x, event.y) targetInnerRv = touchedItem?.findViewById(R.id.rv_dynamic_inner_reporting) // 校验触摸点是否真的落在内层RecyclerView区域内 targetInnerRv?.let { innerRv -> val rvLocation = IntArray(2) innerRv.getLocationOnScreen(rvLocation) val touchRawX = event.rawX.toInt() val touchRawY = event.rawY.toInt() if (touchRawX < rvLocation[0] || touchRawX > rvLocation[0] + innerRv.width || touchRawY < rvLocation[1] || touchRawY > rvLocation[1] + innerRv.height ) { targetInnerRv = null } } } MotionEvent.ACTION_MOVE -> { val deltaY = touchStartY - event.y targetInnerRv?.let { innerRv -> // 判断内层是否还有可滚动空间 val innerCanScroll = when { deltaY > 0 -> innerRv.canScrollVertically(1) // 上滑,判断内层是否可继续向下滚动 deltaY < 0 -> innerRv.canScrollVertically(-1) // 下滑,判断内层是否可继续向上滚动 else -> false } if (innerCanScroll) { // 内层可滚动时,外层不拦截事件,将滑动距离派发给内层 outerRv.requestDisallowInterceptTouchEvent(true) innerRv.scrollBy(0, deltaY.toInt()) touchStartY = event.y return true } } } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { targetInnerRv = null outerRv.requestDisallowInterceptTouchEvent(false) } } return false } override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {} override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {} })
3 注意事项
- 内层列表数据动态刷新后,需要调用
rv_dynamic_inner_reporting.requestLayout()触发重新测量,避免高度计算错误导致内容显示不全 - 该方案不会拦截外层的滚动监听,内层内容滚动到边界后,继续滑动会正常触发外层的分页加载逻辑,无需修改原有分页代码
- 滑动惯性和连贯度和原生RecyclerView效果一致,不会出现卡顿、滑动中断的问题
内容的提问来源于stack exchange,提问作者Said
相关产品推荐
相关产品推荐

