RecyclerView拖拽至其他View时如何获取item的adapterPosition
问题根因
你当前无法获取item位置、无法实现拖拽的核心问题有两个:
- 自定义的
OnItemLongClickListener接口仅回传了Statement数据对象,没有回传item对应的适配器位置、被长按的itemView实例 - 系统拖拽API
startDragAndDrop()要求传入触发拖拽的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
相关产品推荐
相关产品推荐

