You need to enable JavaScript to run this app. Kotlin中同一Activity内两个同适配器RecyclerView实现不同交互的方法
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中同一Activity内两个同适配器RecyclerView实现不同交互的方法

两个RecyclerView复用同一适配器的差异化交互实现

核心误区澄清

这类复用场景下的单套交互适配问题,根源是误用了让Activity统一实现回调接口的写法——这种写法把Activity本身作为唯一的回调实例,两个复用同一适配器的RecyclerView自然会拿到完全相同的回调逻辑。Java开发中常用的“为每个实例单独设置回调”的思路在Kotlin里完全适用,不存在语言层面的兼容问题,也不需要依赖Activity统一实现接口。

可落地实现方案

方案1:保留现有接口结构,适配原Java开发习惯

这种方式完全复用已经写好的适配器代码,不需要大规模重构,只需要为两个适配器分别传入独立的接口实现实例即可。
代码中预留的INSERT_LISTENER_1、INSERT_LISTENER_2不需要绑定到Activity的接口实现上,直接在Activity内定义两个独立的匿名接口实现即可:

// 绑定到当前玩家列表RecyclerView的回调
private val INSERT_LISTENER_1 = object : RoleAdapter.OnItemClick {
    override fun onClick(position: Int) {
        // 实现第一个RV的点击逻辑,比如移除已选中的玩家角色
        currentPlayersAdapter?.removeItem(position)
    }

    override fun onHold(position: Int): Boolean {
        // 实现第一个RV的长按逻辑,返回是否消费长按事件
        return true
    }
}

// 绑定到可选角色列表RecyclerView的回调
private val INSERT_LISTENER_2 = object : RoleAdapter.OnItemClick {
    override fun onClick(position: Int) {
        // 实现第二个RV的点击逻辑,比如将选中角色添加到已选列表
        val selectedRole = availableRolesAdapter?.getList()?.get(position) ?: return
        currentPlayersAdapter?.addItem(selectedRole, currentPlayersAdapter?.itemCount ?: 0)
    }

    override fun onHold(position: Int): Boolean {
        // 实现第二个RV的长按逻辑
        return false
    }
}

这种写法的优势:

  • 和Java开发时的回调逻辑100%一致,学习成本为0
  • 两个回调完全独立,后续如果新增第三个、第四个复用该适配器的RecyclerView,只需要再传入一个新的独立接口实现即可

注意:现有适配器的长按逻辑存在bug,当前代码不管onHold方法返回什么值,都会强制返回true消费长按事件,需要将返回值与接口方法的返回值对齐,修改后的长按逻辑为:

holder.itemView.setOnLongClickListener {
    val adapterPosition = holder.adapterPosition
    // 滑动过程中出现无效位置时直接返回false
    if (adapterPosition == RecyclerView.NO_POSITION) return@setOnLongClickListener false
    listener?.onHold(adapterPosition) ?: false
}

方案2:Kotlin 风格lambda优化写法,去掉冗余接口

如果觉得单独定义回调接口太繁琐,可以直接把点击、长按的回调作为可空属性放到适配器中,通过lambda传递逻辑,写法更简洁。
首先调整适配器结构,移除原有的OnItemClick接口,替换为独立的lambda回调属性:

class RoleAdapter(
    private val context: Context,
    private val list: ArrayList<Role>
) : RecyclerView.Adapter<RoleAdapter.MyViewHolder>() {
    // 直接定义可空lambda回调,无需额外接口
    var onItemClick: ((position: Int) -> Unit)? = null
    var onItemLongClick: ((position: Int) -> Boolean)? = null

    // 保留原有的getItemCount、addItem、removeItem、MyViewHolder逻辑
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        return MyViewHolder(
            LayoutInflater.from(parent.context).inflate(R.layout.item_role, parent, false)
        )
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val item = list[position]
        holder.icon.setImageDrawable(Icons.getDrawableIcon(icon = item.icon!!, context = context))
        holder.text.text = item.name

        // 触发点击回调
        holder.itemView.setOnClickListener {
            val adapterPosition = holder.adapterPosition
            if (adapterPosition != RecyclerView.NO_POSITION) {
                onItemClick?.invoke(adapterPosition)
            }
        }

        // 触发长按回调
        holder.itemView.setOnLongClickListener {
            val adapterPosition = holder.adapterPosition
            if (adapterPosition == RecyclerView.NO_POSITION) return@setOnLongClickListener false
            onItemLongClick?.invoke(adapterPosition) ?: false
        }
    }

    override fun getItemCount(): Int = list.size

    class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val icon: ImageView = itemView.findViewById(R.id.item_icon)
        val text: TextView = itemView.findViewById(R.id.item_text)
    }

    fun addItem(item: Role, position: Int) {
        list.add(item)
        notifyItemInserted(position)
    }

    fun removeItem(position: Int) {
        list.removeAt(position)
        notifyItemRemoved(position)
    }
}

在Activity中设置适配器时,直接通过lambda传入对应逻辑即可:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_new_game)

    currentPlayersRV = findViewById(R.id.current_players_rv)
    availableRolesRV = findViewById(R.id.roles_rv)

    // 初始化已选玩家列表适配器
    currentPlayersAdapter = RoleAdapter(applicationContext, ArrayList<Role>()).apply {
        onItemClick = { position ->
            // 已选列表的点击逻辑
            removeItem(position)
        }
        onItemLongClick = { position ->
            // 已选列表的长按逻辑
            true
        }
    }
    currentPlayersRV?.layoutManager = GridLayoutManager(baseContext, 3)
    currentPlayersRV?.adapter = currentPlayersAdapter

    // 初始化可选角色列表适配器
    availableRolesAdapter = RoleAdapter(applicationContext, Role.getRoles(applicationContext)).apply {
        onItemClick = { position ->
            // 可选列表的点击逻辑
            val selectedRole = list[position]
            currentPlayersAdapter?.addItem(selectedRole, currentPlayersAdapter?.itemCount ?: 0)
        }
        onItemLongClick = { position ->
            // 可选列表的长按逻辑
            false
        }
    }
    availableRolesRV?.layoutManager = GridLayoutManager(baseContext, 3)
    availableRolesRV?.adapter = availableRolesAdapter
}

注意事项

  • 两种方案都不要使用“Activity实现回调接口”的写法,这种写法天生不支持多实例的差异化回调,只是早期教程为了简化代码的偷懒写法,不适合多RV复用适配器的场景
  • Kotlin中使用object关键字创建匿名接口实例的逻辑,和Java中用new XxxInterface(){...}创建匿名对象完全等价,不存在语言层面的兼容性问题,觉得这套写法行不通,本质是没有对应到Java的语法映射关系。

内容的提问来源于stack exchange,提问作者Riadh Adrani

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.08 23:57:05