处理键盘操作

当用户将焦点放到可编辑文本组件(例如 TextField),并且设备连接了硬件键盘时,所有输入都由系统处理。您可以通过处理按键事件来提供键盘快捷键。

默认键盘快捷键

以下键盘快捷键开箱即用。

键盘快捷键 操作 支持快捷键的可组合项
Shift+Ctrl+左箭头/右箭头 选择文本到单词开头/结尾 BasicTextFieldTextField
Shift+Ctrl+上箭头/下箭头 选择文本到段落开头/结尾 BasicTextFieldTextField
Shift+Alt+上箭头/下箭头Shift+Meta+左箭头/右箭头 选择文本到文本开头/结尾 BasicTextFieldTextField
Shift+左箭头/右箭头 选择字符 BasicTextFieldTextField
Ctrl+A 全选 BasicTextFieldTextField
Ctrl+C/Ctrl+X/Ctrl+V 复制/剪切/粘贴 BasicTextFieldTextField
Ctrl+Z/Ctrl+Shift+Z 撤消/重做 BasicTextFieldTextField
PageDown/PageUp 滚动 LazyColumnverticalScroll 修饰符、scrollable 修饰符

按键事件

在 Compose 中,您可以使用 onKeyEvent 修饰符处理单个按键操作。此修饰符接受一个 lambda,当修改后的组件接收到按键事件时,就会调用该 lambda。按键事件被描述为一个 KeyEvent 对象。您可以通过引用传递给 onKeyEvent 修饰符的 lambda 中的对象,获取每个按键事件的信息。

一次按键会发送两个按键事件。一个在用户按下按键时触发;另一个在按键释放时触发。您可以通过引用 KeyEvent 对象的 type 属性来区分这两个按键事件。

onKeyEvent lambda 的返回值指示按键事件是否已处理。如果您的应用处理了按键事件,则返回 true,这将停止事件的传播。

以下代码段展示了当用户在 Box 组件上释放 S 键时如何调用 doSomething() 函数。

Box(
    modifier = Modifier.focusable().onKeyEvent {
        if(
            it.type == KeyEventType.KeyUp &&
            it.key == Key.S
        ) {
            doSomething()
            true
        } else {
            false
        }
    }
)  {
    Text("Press S key")
}

修饰键

KeyEvent 对象具有以下属性,这些属性指示修饰键是否被按下:

在描述您的应用处理的按键事件时要具体。以下代码段仅在用户释放 S 键时才调用 doSomething() 函数。如果用户按下任何修饰键(例如 Shift 键),应用不会调用该函数。

Box(
  modifier = Modifier.focusable().onKeyEvent{
     if(
       it.type == KeyEventType.KeyUp &&
       it.key == Key.S &&
       !it.isAltPressed &&
       !it.isCtrlPressed &&
       !it.isMetaPressed &&
       !it.isShiftPressed
     ) {
       doSomething()
       true
     } else {
       false
     }
  }
)  {
    Text("Press S key with a modifier key")
}

空格键和 Enter 键点击事件

空格键Enter 键也会触发点击事件。例如,用户可以通过如下方式处理点击事件来使用 空格键Enter 键切换媒体播放(播放或暂停):

MoviePlayer(
   modifier = Modifier.clickable { togglePausePlay() }
)

clickable 修饰符会拦截按键事件,并在按下 空格键Enter 键时调用 onClick() 回调。因此,在代码段中,通过按下 空格键Enter 键调用了 togglePausePlay() 函数。

未处理的按键事件

未处理的按键事件会从事件发生的组件传播到外部的封闭组件。在下面的示例中,当释放 S 键时,InnerComponent 会处理按键事件,因此 OuterComponent 不会收到由释放 S 键触发的任何按键事件。这就是为什么 actionB() 函数从未被调用的原因。

InnerComponent 上的其他按键事件(例如释放 D 键)可以由 OuterComponent 处理。actionC() 函数被调用,因为释放 D 键的按键事件已传播到 OuterComponent

OuterComponent(
    modifier = Modifier.onKeyEvent {
        when {
           it.type == KeyEventType.KeyUp && it.key == Key.S -> {
               actionB() // This function is never called.
               true
           }
           it.type == KeyEventType.KeyUp && it.key == Key.D -> {
               actionC()
               true
           }
           else -> false
        }
    }
) {
    InnerComponent(
        modifier = Modifier.onKeyEvent {
            if(it.type == KeyEventType.KeyUp && it.key == Key.S) {
                actionA()
                true
            } else {
                false
            }
        }
    )
}

onKeyPreviewEvent 修饰符

在某些用例中,您希望在按键事件触发默认操作之前拦截它。为 TextField 添加自定义快捷键是一个典型的例子。以下代码段允许用户通过按 Tab 键移动到下一个可聚焦组件。

val focusManager = LocalFocusManager.current
var textFieldValue by remember { mutableStateOf(TextFieldValue()) }

TextField(
    textFieldValue,
    onValueChange = {
        textFieldValue = it
    },
    modifier = Modifier.onPreviewKeyEvent {
        if (it.type == KeyEventType.KeyUp && it.key == Key.Tab) {
            focusManager.moveFocus(FocusDirection.Next)
            true
        } else {
            false
        }
    }
)

默认情况下,TextField 组件会在用户每次按下 Tab 键时添加一个制表符,即使按键事件已通过 onKeyEvent 修饰符处理。要在不添加任何制表符的情况下移动键盘焦点,请在触发与按键事件相关的操作之前处理按键事件,如代码段所示。onKeyPreviewEvent() lambda 通过返回 true 拦截按键事件。

当用户按下 S 键时,不会调用 Box 组件的 onPreviewKeyEvent() 函数,而是调用 previewSKey() 函数。

Column(
  modifier = Modifier.onPreviewKeyEvent{
    if(it.key == Key.S){
      previewSKey()
      true
    }else{
      false
    }
  }
) {
  Box(
    modifier = Modifier
        .focusable()
        .onPreviewKeyEvent {
            actionForPreview(it)
            false
        }
        .onKeyEvent {
            actionForKeyEvent(it)
            true
        }
  ) {
    Text("Press any key")
  }
}

当用户按下 Tab 键时,Box 组件的 onPreviewKeyEvent() lambda 也不会被触发。onPreviewKeyEvent() lambda 会首先在父组件上调用,然后才调用子组件中的 onPreviewKeyEvent()。您可以利用此行为来实现全屏键盘快捷键。

其他资源