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

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,避免全量布局重算:

  1. 创建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
    }
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 17:02:12