You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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 }
    }
}

问题原因分析

从你的代码和现象来看,核心问题出在两个方面:

  1. 触摸事件传递异常:你重写的LinkMovementMethod.onTouchEvent最后强制返回false,即使已经调用了super.onTouchEvent处理文本选择逻辑,返回false会让系统认为该触摸事件未被处理,导致后续的文本选择、上下文菜单触发逻辑无法正常执行。
  2. 视图复用状态未重置:RecyclerView复用视图时,TextView的选中状态、ActionMode状态没有被正确重置,复用的旧状态干扰了新绑定数据的文本选择流程。

解决方案

下面是针对性的修复步骤,无需禁用RecyclerView的视图复用:

1. 修复触摸事件传递逻辑

修改LinkMovementMethodonTouchEvent方法,确保事件处理结果正确返回:

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

火山引擎 最新活动