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

RecyclerView拖拽至其他View时如何获取item的adapterPosition

问题根因

你当前无法获取item位置、无法实现拖拽的核心问题有两个:

  • 自定义的OnItemLongClickListener接口仅回传了Statement数据对象,没有回传item对应的适配器位置、被长按的itemView实例
  • 系统拖拽APIstartDragAndDrop()要求传入触发拖拽的View作为入参,你当前回调逻辑中拿不到对应的itemView,也无法正确构造拖拽阴影,自然没法实现和官方示例一致的效果

修改方案

1. 调整Adapter的长按回调逻辑

首先修改长按事件的接口定义,把需要的参数透传出来,注意位置不要在onBindViewHolder中固定传入,要在长按触发时实时获取bindingAdapterPosition,避免列表增删后位置错乱。
修改后的Adapter核心代码如下:

class StatementAdapter(
    private val context: Context,
    private val statementList: ArrayList<Statement>
) : RecyclerView.Adapter<StatementAdapter.StatementViewHolder>() {

    private var mListener: OnItemLongClickListener? = null

    interface OnItemLongClickListener {
        // 回传三个必要参数:当前item数据、适配器位置、被长按的itemView
        fun onItemLongClick(statement: Statement, position: Int, itemView: View)
    }

    fun setOnItemLongClickListener(listener: OnItemLongClickListener) {
        mListener = listener
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StatementViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        val statementBinding: StatementBinding =
            DataBindingUtil.inflate(layoutInflater, R.layout.statement_list, parent, false)
        return StatementViewHolder(statementBinding, mListener)
    }

    override fun getItemCount(): Int = statementList.size

    override fun onBindViewHolder(holder: StatementViewHolder, position: Int) {
        val statementViewModel = statementList[position]
        holder.bind(statementViewModel)
    }

    class StatementViewHolder(
        private val statementBinding: StatementBinding,
        private val listener: OnItemLongClickListener?
    ) : RecyclerView.ViewHolder(statementBinding.root) {

        fun bind(statementViewModel: Statement) {
            statementBinding.statementModel = statementViewModel
            itemView.setOnLongClickListener { clickedView ->
                // 实时获取当前item的真实位置,忽略无效位置
                val currentPosition = bindingAdapterPosition
                if (currentPosition != RecyclerView.NO_POSITION) {
                    listener?.onItemLongClick(statementViewModel, currentPosition, clickedView)
                }
                true
            }
            statementBinding.executePendingBindings()
        }
    }
}

2. 修改Activity中的回调实现

调整接口实现的方法签名,直接从回调参数中拿到需要的position和itemView,调用系统拖拽API即可,不需要自定义空实现的MyDragShadowBuilder,直接用系统提供的View.DragShadowBuilder即可。
修改后的Activity核心代码如下:

class TrueOrFalseActivity : AppCompatActivity(), StatementAdapter.OnItemLongClickListener {
    private lateinit var trueOrFalseBinding: ActivityTrueOrFalseBinding
    private val dragMessage = "Added"
    private val inBucket = "in bucket"
    private val offBucket = "not in bucket"
    // 直接用DataBinding拿控件,不需要额外findViewById和可空声明
    private val rvStatement get() = trueOrFalseBinding.rvStatement
    private var statementAdapter: StatementAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        trueOrFalseBinding = ActivityTrueOrFalseBinding.inflate(layoutInflater)
        setContentView(trueOrFalseBinding.root)

        val statementViewModel = ViewModelProvider(this).get(StatementViewModel::class.java)
        statementViewModel.generateStatement()

        statementViewModel.newMStatementList.observe(this) { list ->
            statementAdapter = StatementAdapter(this@TrueOrFalseActivity, list)
            rvStatement.layoutManager = LinearLayoutManager(this@TrueOrFalseActivity)
            rvStatement.adapter = statementAdapter
            statementAdapter?.setOnItemLongClickListener(this)
        }
    }

    override fun onItemLongClick(statement: Statement, position: Int, itemView: View) {
        // 这里的position就是你需要的item适配器位置,可直接使用
        val dragData = ClipData(
            dragMessage,
            arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN),
            ClipData.Item(dragMessage)
        )
        // 用传入的itemView构造拖拽阴影,和官方示例逻辑完全一致
        val dragShadow = View.DragShadowBuilder(itemView)
        // 兼容不同版本的拖拽API
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            itemView.startDragAndDrop(dragData, dragShadow, null, 0)
        } else {
            @Suppress("DEPRECATION")
            itemView.startDrag(dragData, dragShadow, null, 0)
        }
    }
}

3. 后续优化建议

  • 不建议每次列表数据更新都新建Adapter实例,可以给Adapter增加updateList方法,配合DiffUtil刷新数据,性能更好也能避免重复绑定监听的问题
  • 拖拽过程中如果需要传递当前item的完整数据,可以把Statement实现序列化接口,放到ClipData中,在目标CardView的拖拽监听中直接取出数据处理,不需要额外持有全局列表引用
  • 给目标CardView设置OnDragListener处理拖拽放置的逻辑即可,和官方普通View的拖拽监听写法完全一致

内容的提问来源于stack exchange,提问作者Mohamed Rimshad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 21:06:33