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

NestedScrollView嵌套RecyclerView中TextView自定义SelectionActionModeCallback遇问题求助

NestedScrollView嵌套RecyclerView文本选择菜单问题解决

场景说明

布局结构为NestedScrollView嵌套RecyclerView,每个RecyclerView条目包含:

  • TextView one
  • ImageView
  • TextView two

通过Kotlin扩展函数自定义TextView的文本选择操作,原代码如下:

fun TextView.customMenuCallBack() {
    var selectAll = false
    customSelectionActionModeCallback = object : ActionMode.Callback {
        override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
            return true
        }

        override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
            return true
        }

        override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
            item?.let {
                return when (it.itemId) {
                    android.R.id.selectAll -> {
                        selectAll = true
                        false
                    }

                    android.R.id.copy -> {
                        if (selectAll) {
                            //TODO set content
                            try {
                                val clipboard: ClipboardManager = context.getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
                                val clip = ClipData.newPlainText(context.toString(), content)
                                clipboard.setPrimaryClip(clip)
                            } catch (e: Exception) {
                                e.printStackTrace()
                            }
                        }
                       
                        //make custom action work return true
                        true
                    }
                    else -> {
                        false
                    }
                }
            }
            return false
        }

        override fun onDestroyActionMode(mode: ActionMode?) {

        }

    }
}

遇到的问题

  • 点击菜单项后菜单无法隐藏;
  • 滚动异常:向前滚动时会突然跳回选中文本附近的位置;
  • 在MIUI系统中无法正常工作。

针对性解决方案

问题1:点击菜单项后菜单无法隐藏

原因:处理完菜单项逻辑后未主动结束ActionMode。
修改代码:在onActionItemClicked中完成逻辑后调用mode?.finish():

fun TextView.customMenuCallBack() {
    var selectAll = false
    customSelectionActionModeCallback = object : ActionMode.Callback {
        override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
            return true
        }

        override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
            return true
        }

        override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
            item?.let {
                return when (it.itemId) {
                    android.R.id.selectAll -> {
                        selectAll = true
                        false // 交给系统处理全选逻辑
                    }

                    android.R.id.copy -> {
                        if (selectAll) {
                            try {
                                val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
                                val clip = ClipData.newPlainText("文本复制", text.toString())
                                clipboard.setPrimaryClip(clip)
                            } catch (e: Exception) {
                                e.printStackTrace()
                            }
                        }
                        mode?.finish() // 结束ActionMode,隐藏菜单
                        return true
                    }
                    else -> false
                }
            }
            return false
        }

        override fun onDestroyActionMode(mode: ActionMode?) {
            selectAll = false // 重置状态
        }
    }
}

问题2:滚动时跳回选中文本位置

原因:文本选中后,TextView会持有焦点并设置滚动锚点,干扰NestedScrollView的滚动逻辑。
解决方案:

  1. 在RecyclerView的Adapter中,回收条目时清除TextView选中状态并结束ActionMode:
override fun onViewRecycled(holder: YourViewHolder) {
    super.onViewRecycled(holder)
    // 清除文本选中状态
    holder.textViewOne.apply {
        textSelectionMode = TextView.SELECTION_MODE_NONE
        setSelection(0, 0)
    }
    holder.textViewTwo.apply {
        textSelectionMode = TextView.SELECTION_MODE_NONE
        setSelection(0, 0)
    }
    // 结束当前ActionMode
    (holder.itemView.context as? AppCompatActivity)?.supportActionMode?.finish()
}
  1. 给NestedScrollView添加属性,避免子View抢占焦点:
<androidx.core.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:descendantFocusability="blocksDescendants">
    
    <!-- 内部RecyclerView等布局 -->
</androidx.core.widget.NestedScrollView>

问题3:MIUI系统中无法正常工作

原因:MIUI对系统ActionMode有自定义修改,默认customSelectionActionModeCallback可能被系统逻辑覆盖。
解决方案:拦截长按事件,自定义ActionMode菜单:

  1. 新建自定义菜单文件res/menu/custom_text_select_menu.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/action_select_all"
        android:title="@string/select_all"/>
    <item
        android:id="@+id/action_copy"
        android:title="@string/copy"/>
</menu>
  1. 修改扩展函数,通过长按触发自定义ActionMode:
fun TextView.customMenuCallBack() {
    setOnLongClickListener {
        // 先选中全部文本
        setSelection(0, text.length)
        // 启动自定义ActionMode
        (context as? AppCompatActivity)?.startActionMode(object : ActionMode.Callback {
            override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
                mode?.menuInflater?.inflate(R.menu.custom_text_select_menu, menu)
                return true
            }

            override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
                return true
            }

            override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
                item?.let {
                    when (it.itemId) {
                        R.id.action_select_all -> {
                            setSelection(0, text.length)
                            mode?.finish()
                            return true
                        }
                        R.id.action_copy -> {
                            try {
                                val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
                                val clip = ClipData.newPlainText("文本复制", text.toString())
                                clipboard.setPrimaryClip(clip)
                            } catch (e: Exception) {
                                e.printStackTrace()
                            }
                            mode?.finish()
                            return true
                        }
                    }
                }
                return false
            }

            override fun onDestroyActionMode(mode: ActionMode?) {
                // 清除选中状态
                setSelection(0, 0)
            }
        })
        true
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 10:10:31