NestedScrollView内嵌两个RecyclerView如何实现已浏览item的追踪?
NestedScrollView内嵌wrap_content RecyclerView的已浏览item追踪方案
方案1:监听NestedScrollView滚动 + 子View可见性判断(最适配当前实现的方案)
当前场景下RecyclerView所有item已全部加载到内存,不需要依赖滚动回调拿可见位置,直接通过嵌套滚动父容器的滚动事件判断即可:
- 给外层
NestedScrollView设置滚动监听,每次滚动触发时分别处理两个RecyclerView的可见性检测 - 对单个RecyclerView,遍历所有已加载的子View,调用
getGlobalVisibleRect()方法判断该item的可见面积占比,当可见面积超过*50%*时,将对应position的item标记为已浏览 - 额外增加已浏览集合做去重,已经标记过的item不再重复参与判断,降低计算开销
核心示例代码:
// 两个RecyclerView分别维护自己的已浏览标记集合 val browsedPos1 = hashSetOf<Int>() val browsedPos2 = hashSetOf<Int>() nestedScrollView.setOnScrollChangeListener { _, _, scrollY, _, _ -> // 处理第一个RecyclerView handleRVBrowsed(rv1, scrollY, browsedPos1) // 处理第二个RecyclerView handleRVBrowsed(rv2, scrollY, browsedPos2) } private fun handleRVBrowsed(rv: RecyclerView, scrollY: Int, browsedSet: HashSet<Int>) { val rvTop = rv.top val rvBottom = rv.bottom val screenHeight = resources.displayMetrics.heightPixels // 当前RecyclerView完全不可见时直接跳过判断 if (rvBottom < scrollY || rvTop > scrollY + screenHeight) return for (i in 0 until rv.childCount) { val child = rv.getChildAt(i) val pos = rv.getChildAdapterPosition(child) if (browsedSet.contains(pos)) continue val rect = Rect() child.getGlobalVisibleRect(rect) val visibleHeight = rect.height() val totalHeight = child.height // 可见占比超过一半即标记为已浏览,阈值可根据需求调整 if (visibleHeight * 1.0f / totalHeight >= 0.5f) { browsedSet.add(pos) // 此处执行自定义的已浏览业务逻辑 } } }
方案2:固定高度item简化计算(性能更高)
如果你的RecyclerView的item是固定高度的,可以不用遍历所有子View,直接通过滚动偏移计算可见position:
- 提前测量单个item的总高度(包含分割线、上下边距)
- 结合NestedScrollView的滚动偏移、RecyclerView自身的top位置,直接计算出当前可见的起始和结束position,直接标记范围即可
方案3:重构布局从根源解决问题(长期最优)
NestedScrollView嵌套wrap_content的RecyclerView本身会失去复用能力,item数量多的话会有明显性能问题,建议重构:
- 用单个RecyclerView实现全部内容,把原来两个RecyclerView的item定义为不同的itemType
- 也可以直接用
ConcatAdapter直接拼接两个原有RecyclerView的Adapter,不需要修改原有item逻辑,即可恢复RecyclerView复用能力,后续直接用LinearLayoutManager的findFirstVisibleItemPosition()、findLastVisibleItemPosition()等常规API就能实现已浏览追踪,性能和可维护性更好
内容的提问来源于stack exchange,提问作者testivanivan
相关产品推荐
相关产品推荐

