键盘事件
在 Compose Multiplatform 桌面版中,您可以通过在两个不同范围设置事件处理程序来管理键盘事件:
- 基于焦点元素的事件处理程序。
- 窗口范围内的事件处理程序。
焦点组件中的事件
这种方法意味着在键盘上按下按键会触发当前焦点组件的事件处理程序。
一个典型的场景是为 TextField 等活跃控件定义键盘处理程序。要在按键事件触发默认操作之前对其进行拦截,您可以使用 onKeyEvent 和 onPreviewKeyEvent 修饰符。使用 onKeyEvent 修饰符可以处理单个按键操作,而 onPreviewKeyEvent 则更适合用于定义快捷键。
以下示例演示了 TextField 的交互,根据按住 时按下的按键执行不同的操作。请将此代码添加到 sharedUI/src/jvmMain/kotlin 中的 main.kt 文件:
kotlin
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.isCtrlPressed
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.window.singleWindowApplication
fun main() = singleWindowApplication (title = "Key events") {
MaterialTheme {
var consumedText by remember { mutableStateOf(0) }
var text by remember { mutableStateOf("") }
Column(Modifier.fillMaxSize(), Arrangement.spacedBy(5.dp)) {
Text("Consumed text: $consumedText")
TextField(
value = text,
onValueChange = { text = it },
modifier = Modifier.onPreviewKeyEvent {
when {
(it.isCtrlPressed && it.key == Key.Minus && it.type == KeyEventType.KeyUp) -> {
consumedText -= text.length
text = ""
true
}
(it.isCtrlPressed && it.key == Key.Equals && it.type == KeyEventType.KeyUp) -> {
consumedText += text.length
text = ""
true
}
else -> false
}
}
)
}
}
}
窗口范围内的事件
要定义在当前窗口中始终处于活跃状态的键盘事件处理程序,请使用 Window、singleWindowApplication 和 Dialog 函数中提供的 onPreviewKeyEvent 和 onKeyEvent 参数。它们的区别在于当事件未被消耗时如何进行分发:onPreviewKeyEvent 将事件分发给它的第一个子项,而 onKeyEvent 则将事件分发给该可组合项的父项。通常,拦截事件首选 onPreviewKeyEvent,因为它甚至可以实现全屏范围的键盘快捷键。
以下示例演示了窗口交互,例如通过按下 Escape 关闭弹出对话框,以及通过按下 快捷键更改窗口内容。请将此代码添加到 sharedUI/src/jvmMain/kotlin 中的 main.kt 文件:
kotlin
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.isCtrlPressed
import androidx.compose.ui.input.key.isShiftPressed
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.type
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogWindow
import androidx.compose.ui.window.singleWindowApplication
private var cleared by mutableStateOf(false)
fun main() = singleWindowApplication(
title = "Keyboard events",
onKeyEvent = {
if (
it.isCtrlPressed &&
it.isShiftPressed &&
it.key == Key.C &&
it.type == KeyEventType.KeyDown
) {
cleared = true
true
} else {
false
}
}
) {
MaterialTheme {
if (cleared) {
Text("The App was cleared!")
} else {
App()
}
}
}
@Composable
fun App() {
var isDialogOpen by remember { mutableStateOf(false) }
if (isDialogOpen) {
DialogWindow(onCloseRequest = { isDialogOpen = false },
title = "Dialog",
onPreviewKeyEvent = {
if (it.key == Key.Escape && it.type == KeyEventType.KeyDown) {
isDialogOpen = false
true
} else {
false
}
}) {
Text("Hello!")
}
}
Column(Modifier.fillMaxSize(), Arrangement.spacedBy(5.dp)) {
Button(
modifier = Modifier.padding(4.dp),
onClick = { isDialogOpen = true }
) {
Text("Open dialog")
}
}
}