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
相关产品推荐
相关产品推荐

