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

Jetpack Compose 如何自定义TextField选中文本的悬浮工具栏菜单

实现方案

Jetpack Compose 的文本选择弹出菜单由TextToolbar接口控制,你只需要重写这个接口的实现,替换全局或者局部的LocalTextToolbar CompositionLocal 即可完成自定义,不需要修改TextField本身的参数。

第一步:自定义TextToolbar实现

class CustomTextToolbar(private val context: Context) : TextToolbar {
    // 存储当前选中区域的信息、文本内容、可选操作回调
    private var currentSelection: TextToolbarContent? = null
    // 菜单的显示状态控制
    private var isMenuVisible by mutableStateOf(false)

    override fun showMenu(
        rect: Rect,
        onCopyRequested: (() -> Unit)?,
        onPasteRequested: (() -> Unit)?,
        onCutRequested: (() -> Unit)?,
        onSelectAllRequested: (() -> Unit)?
    ) {
        // 保存当前选中的信息,用于渲染自定义菜单
        currentSelection = TextToolbarContent(
            selectionRect = rect,
            copyAction = onCopyRequested,
            pasteAction = onPasteRequested,
            cutAction = onCutRequested,
            selectAllAction = onSelectAllRequested
        )
        isMenuVisible = true
    }

    override fun hide() {
        isMenuVisible = false
        currentSelection = null
    }

    override val status: TextToolbarStatus
        get() = if (isMenuVisible) TextToolbarStatus.Shown else TextToolbarStatus.Hidden

    // 用于渲染自定义菜单的Composable函数,你可以在这里写你想要的菜单样式
    @Composable
    fun CustomSelectionMenu() {
        if (isMenuVisible && currentSelection != null) {
            // 用Popup组件将菜单定位到选中文本的位置
            Popup(
                alignment = Alignment.TopStart,
                offset = IntOffset(
                    x = currentSelection!!.selectionRect.left.roundToInt(),
                    y = currentSelection!!.selectionRect.top.roundToInt() - 80 // 调整菜单在选中文本上方显示
                )
            ) {
                // 这里写你自己的菜单布局,比如卡片加横向排列的按钮
                Card(
                    elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
                    shape = RoundedCornerShape(8.dp)
                ) {
                    Row(
                        modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
                        horizontalArrangement = Arrangement.spacedBy(16.dp)
                    ) {
                        // 复制按钮
                        currentSelection?.copyAction?.let { copyAction ->
                            TextButton(onClick = {
                                copyAction()
                                hide()
                            }) {
                                Text("复制")
                            }
                        }
                        // 粘贴按钮
                        currentSelection?.pasteAction?.let { pasteAction ->
                            TextButton(onClick = {
                                pasteAction()
                                hide()
                            }) {
                                Text("粘贴")
                            }
                        }
                        // 你可以在这里加自己的自定义操作,比如分享、搜索等
                        TextButton(onClick = {
                            // 你的自定义操作逻辑
                            hide()
                        }) {
                            Text("自定义操作")
                        }
                    }
                }
            }
        }
    }

    data class TextToolbarContent(
        val selectionRect: Rect,
        val copyAction: (() -> Unit)?,
        val pasteAction: (() -> Unit)?,
        val cutAction: (() -> Unit)?,
        val selectAllAction: (() -> Unit)?
    )
}

第二步:在需要自定义菜单的页面替换LocalTextToolbar

在你使用TextField的页面外层,通过CompositionLocalProvider替换默认的TextToolbar,同时渲染你的自定义菜单:

@Composable
fun CustomTextFieldPage() {
    val context = LocalContext.current
    // 初始化自定义的TextToolbar
    val customTextToolbar = remember { CustomTextToolbar(context) }

    CompositionLocalProvider(
        LocalTextToolbar provides customTextToolbar
    ) {
        // 你的页面内容,里面所有的TextField都会使用自定义的选择菜单
        Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
            TextField(
                value = "测试选中文本",
                onValueChange = {},
                modifier = Modifier.fillMaxWidth()
            )
        }
        // 渲染自定义菜单,必须放在CompositionLocalProvider内部,和页面同层级
        customTextToolbar.CustomSelectionMenu()
    }
}

注意事项

  • 该方案兼容Compose 1.3.0及以上版本,更低版本需要升级Compose版本
  • 你可以根据需求调整菜单的样式、位置、操作按钮,甚至添加图标、分割线等元素
  • 每次点击菜单操作后记得调用hide()方法关闭菜单,避免一直显示
  • 如果只需要给单个TextField自定义菜单,只需要把CompositionLocalProvider包裹在目标TextField外层即可,不会影响其他页面的TextField行为

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 13:15:03