RecyclerView子项文本长按无法弹出复制粘贴上下文菜单的问题求助
RecyclerView子项文本长按无法弹出复制粘贴上下文菜单的问题求助
问题描述
我在使用RecyclerView时遇到一个头疼的问题:长按子项中的文本内容时,复制粘贴的上下文菜单始终无法弹出。但如果给ViewHolder设置holder.setIsRecyclable(false);禁用视图复用,菜单就能正常显示了——显然问题和RecyclerView的视图复用机制有关,但我不想直接禁用复用(会影响性能),想找到正确的修复方式。
我给TextView设置了自定义的文本选择和复制逻辑,代码如下:
fun setTextCustomStyle(textView: TextView) { val context = textView.context textView.highlightColor = context.getColor(R.color.mj_color_black_08_transparent) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { textView.setTextSelectHandleLeft(R.drawable.text_select_handle_left_mtrl) textView.setTextSelectHandleRight(R.drawable.text_select_handle_right_mtrl) textView.setTextSelectHandleMiddle(R.drawable.text_select_handle_middle_mtrl) } textView.movementMethod = object : LinkMovementMethod() { override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean { if (widget.isTextSelectable) { super.onTouchEvent(widget, buffer, event) } return false } } textView.customSelectionActionModeCallback = object : ActionMode.Callback { override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { Selection.selectAll(textView.text as? Spannable) return true } override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { return false } override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { if (item.itemId == android.R.id.copy) { val plainText = (textView.text ?: "").subSequence(textView.selectionStart, textView.selectionEnd).toString() val clipboardManager = context.getSystemService(ClipboardManager::class.java) as ClipboardManager clipboardManager.apply { val clip = ClipData.newPlainText("plainText", plainText) setPrimaryClip(clip) } ToastUtil.show(R.string.already_copy) mode.finish() return true } return false } override fun onDestroyActionMode(mode: ActionMode?) { if (textView is EditText) { textView.requestFocus() } } } textView.setTextIsSelectable(true) if (textView is EditText) { textView.setOnLongClickListener { if (textView.text.isNullOrEmpty() && textView.isFocused) { textView.setSelection(0) } else { textView.selectAll() } false } } else { textView.setOnLongClickListener { false } } }
问题原因分析
从你的代码和现象来看,核心问题出在两个方面:
- 触摸事件传递异常:你重写的
LinkMovementMethod.onTouchEvent最后强制返回false,即使已经调用了super.onTouchEvent处理文本选择逻辑,返回false会让系统认为该触摸事件未被处理,导致后续的文本选择、上下文菜单触发逻辑无法正常执行。 - 视图复用状态未重置:RecyclerView复用视图时,TextView的选中状态、ActionMode状态没有被正确重置,复用的旧状态干扰了新绑定数据的文本选择流程。
解决方案
下面是针对性的修复步骤,无需禁用RecyclerView的视图复用:
1. 修复触摸事件传递逻辑
修改LinkMovementMethod的onTouchEvent方法,确保事件处理结果正确返回:
textView.movementMethod = object : LinkMovementMethod() { override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean { if (widget.isTextSelectable) { // 调用父类处理文本选择触摸事件,并返回处理结果 return super.onTouchEvent(widget, buffer, event) } // 文本不可选时,返回false不处理事件 return false } }
原代码中调用super后仍返回false,相当于“吞掉”了事件的处理结果,系统无法感知到文本选择逻辑已经执行,自然不会触发上下文菜单。
2. 视图复用时重置文本状态
在Adapter的onBindViewHolder方法中,绑定数据前先重置TextView的选中状态和焦点,避免旧状态干扰:
override fun onBindViewHolder(holder: YourViewHolder, position: Int) { // 重置TextView状态 holder.textView.clearFocus() holder.textView.setSelection(0, 0) // 清除之前的文本选中范围 holder.textView.actionMode?.finish() // 销毁可能存在的旧ActionMode // 然后绑定数据并调用你的setTextCustomStyle方法 val data = dataList[position] holder.textView.text = data.content setTextCustomStyle(holder.textView) }
3. 优化自定义ActionMode回调逻辑
调整onCreateActionMode的选中文本逻辑,避免强制全选导致的冲突,适配用户手动选中的范围:
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { val spannable = textView.text as? Spannable ?: return false if (textView.selectionStart == textView.selectionEnd) { // 若用户未手动选中内容,默认全选 Selection.selectAll(spannable) } else { // 保留用户手动选中的文本范围 Selection.setSelection(spannable, textView.selectionStart, textView.selectionEnd) } return true }
4. 移除冗余的OnLongClickListener
对于非EditText的TextView,你设置的setOnLongClickListener { false }会干扰系统默认的长按选中文本逻辑,建议直接移除这段代码,让系统处理长按事件:
// 移除以下冗余代码 // } else { // textView.setOnLongClickListener { false } // }
验证效果
完成以上修改后,你可以测试RecyclerView的子项文本长按:
- 正常滚动复用视图后,长按文本应该能正常弹出复制粘贴菜单
- 复制功能可以正常工作,且不会影响RecyclerView的复用性能
内容来源于stack exchange




