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

如何为RecyclerView添加生命周期内固定不变的首尾不可修改元素

如何为RecyclerView添加生命周期内固定不变的首尾不可修改元素

嘿,这个需求我刚好处理过!你想给RecyclerView的首尾各加一个固定不变的元素,不管中间列表怎么增删改,它们都牢牢待在原地对吧?其实不用复杂操作,只需要调整Adapter的几个核心方法就能实现,还能保持原有业务逻辑的干净。

核心思路

我们不用修改原始的业务数据列表,而是在Adapter层面“虚拟”添加首尾元素:通过调整getItemCount来增加两个位置,然后在onBindViewHolder里根据位置判断是渲染固定元素还是业务数据,同时确保固定元素的交互被禁用,满足“不可修改”的要求。

修改后的完整Adapter代码

下面是基于你提供的代码改造后的版本,我会标注关键改动:

class CategoryRecyclerAdapter(
    private val onItemLongClickListener: (Category) -> Unit = { },
    private val onItemClickListener: (Category) -> Unit = { }
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    // 定义固定的首尾元素(这里用Category示例,你也可以自定义专属类区分)
    private val headerItem = Category(name = "固定头部")
    private val footerItem = Category(name = "固定尾部")
    private val items: MutableList<Category> = mutableListOf()

    // 定义不同的视图类型,区分首尾和普通元素
    companion object {
        private const val VIEW_TYPE_HEADER = 0
        private const val VIEW_TYPE_NORMAL = 1
        private const val VIEW_TYPE_FOOTER = 2
    }

    override fun getItemViewType(position: Int): Int {
        return when (position) {
            0 -> VIEW_TYPE_HEADER
            getItemCount() - 1 -> VIEW_TYPE_FOOTER
            else -> VIEW_TYPE_NORMAL
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            VIEW_TYPE_HEADER -> {
                // 加载头部布局(如果和普通布局一样,也可以复用CategoryItemBinding)
                val binding = CategoryItemBinding.inflate(inflater, parent, false)
                HeaderViewHolder(binding)
            }
            VIEW_TYPE_FOOTER -> {
                // 加载尾部布局
                val binding = CategoryItemBinding.inflate(inflater, parent, false)
                FooterViewHolder(binding)
            }
            else -> {
                val binding = CategoryItemBinding.inflate(inflater, parent, false)
                NormalViewHolder(binding)
            }
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is NormalViewHolder -> {
                // 普通元素的索引要减1,因为头部占了第一个位置
                val realItem = items[position - 1]
                holder.bind(realItem)
            }
            is HeaderViewHolder -> holder.bind(headerItem)
            is FooterViewHolder -> holder.bind(footerItem)
        }
    }

    // 总数量=业务数据数量+2(首尾各一个)
    override fun getItemCount(): Int = items.size + 2

    // 普通元素的ViewHolder,保留原有绑定逻辑
    inner class NormalViewHolder(private val binding: CategoryItemBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(item: Category) {
            binding.root.setOnLongClickListener {
                onItemLongClickListener(item)
                true
            }
            binding.root.setOnClickListener {
                onItemClickListener(item)
            }
            binding.nameTextView.text = item.name
        }
    }

    // 头部ViewHolder,禁用交互确保不可修改
    inner class HeaderViewHolder(private val binding: CategoryItemBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(item: Category) {
            binding.nameTextView.text = item.name
            // 禁用点击和长按事件,防止用户误操作
            binding.root.isClickable = false
            binding.root.isLongClickable = false
        }
    }

    // 尾部ViewHolder,同样禁用交互
    inner class FooterViewHolder(private val binding: CategoryItemBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(item: Category) {
            binding.nameTextView.text = item.name
            binding.root.isClickable = false
            binding.root.isLongClickable = false
        }
    }

    fun set(elements: List<Category>) {
        val oldSize = items.size
        items.clear()
        items.addAll(elements)
        // 只通知中间业务数据的变化,首尾元素不受影响
        notifyItemRangeRemoved(1, oldSize)
        notifyItemRangeInserted(1, elements.size)
    }
}

关键改动说明

  1. 视图类型区分:通过getItemViewType标记首尾元素的类型,方便在onCreateViewHolder中加载对应的布局(如果首尾布局和普通元素不同,直接替换对应的Binding即可)。
  2. 数量调整:getItemCount返回业务数据数量+2,给首尾元素预留位置。
  3. 索引偏移:普通元素的索引需要减1,因为头部占了第一个位置,确保从业务列表中取到正确的数据。
  4. 交互禁用:在首尾元素的ViewHolder中设置isClickable和isLongClickable为false,彻底禁止用户修改或操作这些固定元素。
  5. 数据更新优化:set方法只通知中间业务数据的增删变化,首尾元素不需要重新渲染,提升性能。

这样改造后,不管你调用多少次set方法更新中间的业务数据,首尾的固定元素都会一直存在,并且无法被用户操作,完美满足你的需求!

备注:内容来源于stack exchange,提问作者AlexVs86

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.22 08:54:29