Jetpack Compose for Desktop 如何最优实现弹出菜单展示?
1. 调整PopupMenu接口与实现
首先需要给现有PopupMenu新增偏移量参数,用来控制菜单弹出位置,修改后的代码如下:
通用接口调整
@Composable expect fun PopupMenu( menuItems: List<String>, onClickCallbacks: List<() -> Unit>, showMenu: Boolean, onDismiss: () -> Unit, toggle: @Composable () -> Unit, popupOffset: Offset = Offset.Zero // 新增偏移量参数 )
桌面端实现调整
@Composable actual fun PopupMenu( menuItems: List<String>, onClickCallbacks: List<() -> Unit>, showMenu: Boolean, onDismiss: () -> Unit, toggle: @Composable () -> Unit, popupOffset: Offset = Offset.Zero ) { val density = LocalDensity.current DropdownMenu( expanded = showMenu, onDismissRequest = { onDismiss() }, // 把像素偏移转换为dp偏移传入DropdownMenu offset = with(density) { DpOffset(popupOffset.x.toDp(), popupOffset.y.toDp()) } ) { menuItems.forEachIndexed { index, item -> DropdownMenuItem(onClick = { onDismiss() onClickCallbacks[index] }) { Text(text = item) } } } }
2. 实现OneCard组件的触发逻辑
新增状态控制菜单显隐、记录弹出位置,替换原有的selectable修饰符为手势监听修饰符,处理右键点击和长按事件,完整代码如下:
@Composable fun OneCard(content: MutableState<TextFieldValue>, onValueChange: (TextFieldValue) -> Unit ) { // 控制菜单显隐的状态 val showMenu = remember { mutableStateOf(false) } // 存储菜单弹出的位置坐标 val menuOffset = remember { mutableStateOf(Offset.Zero) } // 系统剪贴板管理器,用来实现文本复制粘贴逻辑,按需引入 val clipboardManager = LocalClipboardManager.current // 自定义菜单项和对应的点击回调,可根据业务需求替换 val menuItems = listOf("复制", "剪切", "粘贴") val menuCallbacks = listOf( // 复制选中的文本 { val selectedText = content.value.selection.normalized().let { content.value.text.substring(it.min, it.max) } clipboardManager.setText(AnnotatedString(selectedText)) }, // 剪切选中的文本 { val selection = content.value.selection.normalized() val selectedText = content.value.text.substring(selection.min, selection.max) clipboardManager.setText(AnnotatedString(selectedText)) onValueChange( content.value.copy( text = content.value.text.removeRange(selection.min, selection.max), selection = TextRange(selection.min) ) ) }, // 粘贴剪贴板中的文本 { val clipboardText = clipboardManager.getText()?.text ?: return@listOf val selection = content.value.selection onValueChange( content.value.copy( text = content.value.text.replaceRange(selection.min, selection.max, clipboardText), selection = TextRange(selection.min + clipboardText.length) ) ) } ) Card(modifier = Modifier.draggable(DraggableState { }, Orientation.Vertical) .padding(16.dp)) { // 将TextField作为触发组件传入PopupMenu PopupMenu( menuItems = menuItems, onClickCallbacks = menuCallbacks, showMenu = showMenu.value, onDismiss = { showMenu.value = false }, popupOffset = menuOffset.value ) { OutlinedTextField( value = content.value, onValueChange = onValueChange, placeholder = { Text("Your thoughts?") }, modifier = Modifier .fillMaxWidth() .pointerInput(Unit) { // 处理长按触发逻辑 coroutineScope { while (true) { val downEvent = awaitPointerEventScope { awaitFirstDown() } val longPressJob = launch { delay(500) // 长按触发延迟可自行调整,单位为毫秒 menuOffset.value = downEvent.position showMenu.value = true } // 手势提前结束时取消长按触发 awaitPointerEventScope { waitForUpOrCancellation() longPressJob.cancel() } } } // 处理右键点击触发逻辑 detectTapGestures( onSecondaryTap = { tapOffset -> menuOffset.value = tapOffset showMenu.value = true } ) } ) } } }
补充说明
- 上述代码已经修复了原实现中placeholder参数的写法错误
- 长按触发的延迟时间可以自行调整
delay(500)中的数值 - 如果不需要文本操作功能,可以直接替换
menuItems和menuCallbacks为你需要的业务逻辑 - 如果需要调整菜单弹出的位置偏移,直接修改
menuOffset.value的数值即可
内容的提问来源于stack exchange,提问作者Hakanai
相关产品推荐
相关产品推荐

