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
相关产品推荐
相关产品推荐

