优化NestedScrollView内wrap_content高度RecyclerView的性能
兄弟,这个问题我之前在做类似的嵌套滚动布局时也踩过坑!核心原因其实是RecyclerView设置为wrap_content时,每次调用submitList都会触发全量的子View测量——毕竟RecyclerView需要重新计算自身的总高度,而NestedScrollView的存在又会让这个测量过程的开销被放大,哪怕你的子视图很轻量,30条的总数量也足够导致丢帧了。硬编码高度之所以没问题,是因为跳过了这个耗时的测量步骤。
既然不能合并三个RecyclerView,那咱们可以从减少测量开销的角度入手,给你几个可行的优化方案:
方案1:开启固定尺寸优化(最简单的尝试)
先给每个RecyclerView加上setHasFixedSize(true),这个方法会告诉RecyclerView:内容变化时,自身的宽高不会发生改变。虽然你用的是wrap_content,但如果你的列表条目高度是固定的,这个设置能大幅减少RecyclerView的重测量次数,从而缓解丢帧。
代码示例:
// 初始化每个RecyclerView时调用 recyclerView.setHasFixedSize(true)
如果你的条目高度不固定,这个方案的效果可能有限,但值得先试一下,毕竟成本极低。
方案2:自定义LayoutManager缓存测量高度(最优雅的方案)
自定义一个LinearLayoutManager,重写onMeasure方法,缓存上次测量得到的高度。只有当Adapter的数据真正变化时,才重新测量;如果只是重复submit相同的列表,直接复用缓存的高度,跳过全量测量。
代码示例:
class CachedLinearLayoutManager(context: Context) : LinearLayoutManager(context) { private var cachedHeight = 0 override fun onMeasure(recycler: RecyclerView.Recycler, state: RecyclerView.State, widthSpec: Int, heightSpec: Int) { // 只有当数据变化时,才重新测量 if (state.itemCount != itemCount || cachedHeight == 0) { super.onMeasure(recycler, state, widthSpec, heightSpec) cachedHeight = measuredHeight } else { // 复用缓存的高度,避免重测量 setMeasuredDimension(View.MeasureSpec.getSize(widthSpec), cachedHeight) } } }
然后给你的RecyclerView设置这个自定义LayoutManager:
recyclerView.layoutManager = CachedLinearLayoutManager(context)
这个方案能从根源上减少不必要的测量操作,尤其适合需要动态高度但数据更新频率不高的场景。
方案3:动态计算并设置RecyclerView高度(最直接的方案)
既然总条目只有30条,而且子视图轻量化,我们可以在submitList之后,手动计算所有条目的高度总和,然后给RecyclerView设置这个计算出来的高度,替代wrap_content——相当于动态版的“硬编码高度”。
代码示例:
// 假设你已经有了Adapter和RecyclerView的引用 adapter.submitList(newList) { // 列表更新完成后计算高度 recyclerView.post { var totalHeight = 0 val state = RecyclerView.State() for (i in 0 until adapter.itemCount) { val itemView = adapter.createViewHolder(recyclerView, adapter.getItemViewType(i)).itemView itemView.measure( View.MeasureSpec.makeMeasureSpec(recyclerView.width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) totalHeight += itemView.measuredHeight // 加上ItemDecoration的间距(如果有的话) recyclerView.itemDecorationCount.takeIf { it > 0 }?.let { val decoration = recyclerView.getItemDecorationAt(0) val outRect = Rect() decoration.getItemOffsets(outRect, itemView, recyclerView, state) totalHeight += outRect.top + outRect.bottom } } // 设置RecyclerView的高度 val params = recyclerView.layoutParams params.height = totalHeight recyclerView.layoutParams = params } }
这个方案的好处是完全避免了wrap_content带来的测量开销,但如果你的条目高度会随内容动态变化(比如有多行文本),需要确保测量时用的是真实的内容数据。
方案4:关闭RecyclerView的嵌套滚动(辅助优化)
给每个RecyclerView加上setNestedScrollingEnabled(false),让NestedScrollView全权处理滚动事件,这样可以减少RecyclerView在滚动过程中的内部计算,虽然主要解决的是滚动时的流畅度,但也能间接缓解submitList时的性能压力。
代码示例:
recyclerView.isNestedScrollingEnabled = false
你可以先从方案1和方案4开始尝试,这两个几乎没有代码侵入性;如果效果不够,再试试方案2或方案3,根据你的实际场景选择最合适的就行!
内容的提问来源于stack exchange,提问作者beigirad

