Horizontal RecyclerView设置自定义Item宽度引发自动滚动异常
横向RecyclerView更新数据自动滚顶问题解决
问题现象
为横向RecyclerView填充新列表数据时,会自动滚动至顶部;仅当Item设置自定义宽度时出现此问题,设为MATCH_PARENT则正常。尝试过notifyItemRangeChanged、notifyItemChanged均无效。
核心原因
调用notifyDataSetChanged会触发RecyclerView全量布局重算。自定义Item宽度时,RecyclerView无法正确保存之前的滚动偏移量,重算布局后会默认重置到列表顶部;而MATCH_PARENT的Item宽度与父容器一致,布局计算逻辑简单,滚动位置能被正确保留。
解决方案
方案1:手动保存并恢复滚动位置
在更新数据前后,手动记录当前滚动的位置和偏移量,数据更新完成后再恢复:
// 更新前保存滚动状态 val layoutManager = recyclerViewSubscriptions.layoutManager as LinearLayoutManager val firstVisiblePos = layoutManager.findFirstVisibleItemPosition() val firstVisibleView = layoutManager.findViewByPosition(firstVisiblePos) val scrollOffset = firstVisibleView?.left ?: 0 // 更新数据 subscriptionAdapter?.setData(newSubscriptionList, true, null, false) // 恢复滚动位置(用post确保布局已完成) recyclerViewSubscriptions.post { layoutManager.scrollToPositionWithOffset(firstVisiblePos, scrollOffset) }
同时建议修改Adapter的setData方法,避免直接替换列表后调用notifyDataSetChanged,如果是全量替换,可以先清空旧列表再添加新数据,用notifyItemRangeInserted替代:
fun setData( users: MutableList<SubscribtionListItem>?, hideButtons: Boolean, activeSubscriptionType: Int?, isPendingRequest: Boolean, isPlanReplaced: Boolean = false, standardPlanDuration: Int = 0 ) { this.subscribtionListItem?.clear() users?.let { this.subscribtionListItem?.addAll(it) } // 保存其他参数 this.hideButtons = hideButtons this.activeSubscriptionType = activeSubscriptionType // ...其他参数赋值 notifyItemRangeInserted(0, subscribtionListItem?.size ?: 0) }
方案2:用DiffUtil实现局部更新(推荐)
改用ListAdapter配合DiffUtil,自动计算新旧数据差异,只更新变化的Item,避免全量布局重算:
- 创建DiffCallback:
class SubscribtionDiffCallback : DiffUtil.ItemCallback<SubscribtionListItem>() { override fun areItemsTheSame(oldItem: SubscribtionListItem, newItem: SubscribtionListItem): Boolean { // 根据你的数据结构,判断是否为同一个Item(比如用唯一ID) return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: SubscribtionListItem, newItem: SubscribtionListItem): Boolean { // 比较Item内容是否完全一致 return oldItem == newItem } }
- 修改Adapter继承
ListAdapter:
class SubscriptionAdapter : ListAdapter<SubscribtionListItem, SubscriptionAdapter.ViewHolder>(SubscribtionDiffCallback()) { // 定义需要的成员变量 var hideButtons: Boolean = false var activeSubscriptionType: Int? = null // ...其他参数 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { // 你的ViewHolder创建逻辑 } override fun onBindViewHolder(holder: ViewHolder, position: Int) { // 绑定数据时使用当前的参数(hideButtons等) val item = getItem(position) // ...绑定逻辑 } fun setData( users: MutableList<SubscribtionListItem>?, hideButtons: Boolean, activeSubscriptionType: Int?, isPendingRequest: Boolean, isPlanReplaced: Boolean = false, standardPlanDuration: Int = 0 ) { // 更新成员参数 this.hideButtons = hideButtons this.activeSubscriptionType = activeSubscriptionType // ...其他参数赋值 // 提交新列表,DiffUtil自动处理更新 submitList(users?.toList()) } // 你的ViewHolder类 inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) }
用submitList提交新数据时,RecyclerView只会更新有变化的Item,不会触发全量布局重算,自然不会丢失滚动位置。
内容的提问来源于stack exchange,提问作者Shikhar
相关产品推荐
相关产品推荐

