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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 23:27:31