嵌套RecyclerView含多视图类型首次滚动卡顿掉帧问题如何解决?
卡顿原因及修复方案
1. 禁止在ViewHolder的bind方法中重复初始化子RecyclerView
你当前每次绑定LottoPrizeOtherListHolder时,都会重新创建GridLayoutManager、重新给子RecyclerView设置adapter,这会导致每次条目滚动到可视区域时都要重新走子列表的初始化、测量逻辑,是卡顿的核心原因。
修改方案:把子RecyclerView的初始化逻辑移到ViewHolder的构造方法中,bind方法只更新子列表的数据:
class LottoPrizeOtherListHolder( private val binding: ItemLottoPrizesOtherListBinding, private val lottoLatestPrizeOtherAdapter: LottoLatestPrizeOtherAdapter, viewPool: RecyclerView.RecycledViewPool ) : RecyclerView.ViewHolder(binding.root) { // 构造方法中一次性初始化子RecyclerView init { binding.prizeOtherRecyclerView.apply { layoutManager = GridLayoutManager(context,2,GridLayoutManager.VERTICAL,false) adapter = lottoLatestPrizeOtherAdapter setRecycledViewPool(viewPool) // 根据单屏展示的子项数量调整固定预取数量,不要每次bind都修改 initialPrefetchItemCount = 4 // 禁用子RecyclerView的嵌套滚动,避免滑动冲突和多余的事件处理 isNestedScrollingEnabled = false } } fun bind(lottoLatestPrizeOther: LottoPrizesLatestModel.LottoLatestPrizeOther) = with(binding) { // bind方法仅做数据更新 lottoLatestPrizeOtherAdapter.numberList = lottoLatestPrizeOther.prizeModel.number.toMutableList() // 建议子Adapter也接入DiffUtil做局部刷新,替代全量notifyDataSetChanged lottoLatestPrizeOtherAdapter.notifyDataSetChanged() titlePrizeOtherTextView.text = lottoLatestPrizeOther.prizeModel.name textPrizeOneNearTextView.text = itemView.context.getString( R.string.lotto_prize_per_baht, lottoLatestPrizeOther.prizeModel.reward ) } }
对应父Adapter的onCreateViewHolder创建该Holder的时候传入共享的viewPool即可,同时必须删除onBindViewHolder里的viewPool.putRecycledView(holder)代码,这段是完全错误的调用,会破坏父RecyclerView本身的回收复用逻辑。
2. 优化子RecyclerView的测量性能
你当前子RecyclerView的高度设置为wrap_content,会导致每次更新数据时都要测量所有子项的高度,可根据业务场景选择两种优化方案:
- 如果每个子条目的高度固定,给子RecyclerView的子项设置固定高度,同时给子LayoutManager开启
setHasFixedSize(true) - 如果子列表的总高度不高,直接把父条目里的
RecyclerView替换成GridView/FlowLayout等静态布局,避免嵌套RecyclerView的额外开销
3. 替换全量刷新为局部刷新
你当前给latestList赋值时调用notifyDataSetChanged()全量刷新,会导致所有可见条目都重新绑定,效率极低。接入DiffUtil实现局部刷新:
var latestList: MutableList<LottoPrizesLatestModel> = mutableListOf() set(value) { val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() { override fun getOldListSize() = field.size override fun getNewListSize() = value.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { // 替换为你的条目唯一标识判断逻辑 return field[oldItemPosition].id == value[newItemPosition].id } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { // 替换为你的条目内容相等判断逻辑 return field[oldItemPosition] == value[newItemPosition] } }) field = value diffResult.dispatchUpdatesTo(this) }
4. 开启父RecyclerView的预取优化
给父RecyclerView的LinearLayoutManager开启预取:
lottoRecyclerView.apply { layoutManager = LinearLayoutManager(context).apply { // 开启嵌套RecyclerView的预取 isItemPrefetchEnabled = true // 调整预取数量,根据你单屏展示的父条目数量+2即可 prefetchDistance = 3 } adapter = lottoLatestAdapter // 如果父条目的高度固定,开启这个可以避免不必要的布局重算 setHasFixedSize(true) }
做完以上修改后首次滚动的卡顿问题会完全消失。
内容的提问来源于stack exchange,提问作者Yothin Homjan
相关产品推荐
相关产品推荐

