在大屏设备上,用户通常会使用键盘、鼠标、触控板、触控笔或游戏手柄与应用互动。如需让应用能够接受来自外部设备的输入,请执行以下操作:
- 测试基本键盘支持,例如 Ctrl+Z 表示撤消,Ctrl+C 表示复制,Ctrl+S 表示保存。如需查看默认键盘快捷键列表,请参阅处理键盘操作。
- 测试高级键盘操作支持,例如通过 Tab 键和箭头键进行键盘导航、按 Enter 键确认文字输入以及在媒体应用中使用空格键执行播放和暂停操作。
- 测试基本的鼠标交互,包括通过右键点击打开上下文菜单、悬停鼠标时图标的变化,以及自定义组件上的鼠标滚轮或触控板滚动事件等等。
- 测试应用专用的输入设备,如触控笔、游戏控制器和音乐应用 MIDI 控制器。
- 考虑支持高级输入方式,让您的应用在桌面环境中脱颖而出,例如,支持将触控板用作 DJ 应用的唱片平滑转换器、让游戏支持鼠标捕捉,以及支持为以键盘为主要输入方式的用户设置键盘快捷键。
键盘
应用对键盘输入的响应方式会影响大屏设备上的用户体验。有三种类型的键盘输入:导航、按键和快捷键。
导航
键盘导航很少在以触控为中心的应用中实现,但用户在使用应用且双手放在键盘上时,会期望能够使用键盘导航。对于有无障碍需求的用户,键盘导航在手机、平板电脑、可折叠设备和桌面设备上至关重要。
对于许多应用,箭头键和 Tab 键导航由 Android 框架自动处理。例如,某些可组合项默认可聚焦,例如 Button 或带有 clickable 修饰符的可组合项;键盘导航通常无需任何额外代码即可正常运行。如需为默认情况下不可聚焦的自定义可组合项启用键盘导航,请添加 focusable 修饰符:
var color by remember { mutableStateOf(Green) } Box( Modifier .background(color) .onFocusChanged { color = if (it.isFocused) Blue else Green } .focusable() ) { Text("Focusable 1") }
如需了解详情,请参阅使可组合项可聚焦。
启用焦点后,Android 框架会根据所有可聚焦组件的位置为其创建导航映射。这通常会按预期运行,无需进一步开发。
不过,对于标签页和列表等复杂的可组合项,Compose 并不总是能确定正确的下一个标签页导航项,例如,当其中一个可组合项是未完全可见的水平可滚动项时。
如需控制焦点行为,请将 focusGroup 修饰符添加到可组合项集合的父级可组合项中。焦点会先移到该组,然后遍历该组,最后再移到下一个可聚焦组件,例如:
Row {
Column(Modifier.focusGroup()) {
Button({}) { Text("Row1 Col1") }
Button({}) { Text("Row2 Col1") }
Button({}) { Text("Row3 Col1") }
}
Column(Modifier.focusGroup()) {
Button({}) { Text("Row1 Col2") }
Button({}) { Text("Row2 Col2") }
Button({}) { Text("Row3 Col2") }
}
}
如需了解详情,请参阅使用焦点小组提供连贯的导航。
仅使用键盘测试对应用中每个界面元素的访问。在没有鼠标输入或触控输入的情况下,应该也可以访问常用元素。
请注意,对于需要使用无障碍功能的用户来说,键盘支持可能是必不可少的。
按键
对于由屏幕虚拟键盘 (IME) 处理的文本输入,例如
TextField
,应用在大屏设备上应按预期运行,无需进行额外的开发工作。对于框架无法预期的按键,应用需要自行处理相应行为。对于具有自定义视图的应用来说尤其如此。
例如,聊天应用使用 Enter 键发送消息,媒体应用使用空格键开始和停止播放,游戏使用 w、a、s 和 d 键控制移动。
您可以使用 onKeyEvent 修饰符处理单个按键,该修饰符接受一个 lambda,当修改后的组件收到按键事件时,系统会调用该 lambda。借助 KeyEvent#type 属性,您可以确定事件是按键 (KeyDown) 还是松开按键 (KeyUp):
Box(
modifier = Modifier.focusable().onKeyEvent {
if(
it.type == KeyEventType.KeyUp &&
it.key == Key.S
) {
doSomething()
true
} else {
false
}
}
) {
Text("Press S key")
}
或者,您也可以替换 onKeyUp() 回调,并为每个收到的键码添加预期行为:
kotlin
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
return when (keyCode) {
KeyEvent.KEYCODE_ENTER -> {
sendChatMessage()
true
}
KeyEvent.KEYCODE_SPACE -> {
playOrPauseMedia()
true
}
else -> super.onKeyUp(keyCode, event)
}
}
当按键被释放时,会发生 onKeyUp 事件。使用回调可防止应用在按键被按住或缓慢松开时需要处理多个 onKeyDown 事件。需要检测按键按下时刻或用户是否按住按键的游戏和应用可以监听 onKeyDown 事件,并自行处理重复的 onKeyDown 事件。
如需了解详情,请参阅处理键盘操作。
快捷键
使用硬件键盘时,用户希望能够使用包含 Ctrl、Alt、Shift 和 Meta 键的常用键盘快捷键。如果应用未实现快捷方式,用户可能会感到沮丧。高级用户也喜欢使用快捷方式来执行应用特有的常用任务。快捷方式可让应用更易于使用,并使其有别于没有快捷方式的应用。
一些常见的快捷键包括 Ctrl+S(保存)、Ctrl+Z(撤消)和 Ctrl+Shift+Z(重做)。如需查看默认快捷键列表,请参阅处理键盘操作。
KeyEvent 对象具有以下属性,用于指示是否按下了辅助键:
例如:
Box(
Modifier.onKeyEvent {
if (it.isAltPressed && it.key == Key.A) {
println("Alt + A is pressed")
true
} else {
false
}
}
.focusable()
)
详情请参阅以下内容:
触控笔
许多大屏设备都配有触控笔。Android 应用会将触控笔输入作为触摸屏输入进行处理。某些设备可能还配有 USB 或蓝牙绘图板,例如 Wacom Intuos。Android 应用可以接收蓝牙输入,但无法接收 USB 输入。
如需访问触控笔 MotionEvent 对象,请向绘图 surface 添加 pointerInteropFilter 修饰符。实现一个 ViewModel 类,其中包含一个处理动作事件的方法;将该方法作为 pointerInteropFilter 修饰符的 onTouchEvent lambda 传递:
@Composable
@OptIn(ExperimentalComposeUiApi::class)
fun DrawArea(modifier: Modifier = Modifier) {
Canvas(modifier = modifier
.clipToBounds()
.pointerInteropFilter {
viewModel.processMotionEvent(it)
}
) {
// Drawing code here.
}
}
MotionEvent 对象包含有关事件的信息:
MotionEvent#getToolType()会返回TOOL_TYPE_FINGER、TOOL_TYPE_STYLUS或TOOL_TYPE_ERASER,具体取决于与显示屏接触的工具MotionEvent#getPressure()报告施加到触控笔的物理压力(如果支持)MotionEvent#getAxisValue()与MotionEvent.AXIS_TILT和MotionEvent.AXIS_ORIENTATION一起使用,可提供触控笔的物理倾斜度和方向(如果支持)
历史点
Android 会对输入事件进行批处理,并且每帧传送一次。触控笔报告事件的频率可能远高于显示屏。创建绘图应用时,请使用 getHistorical API 检查最近可能发生的事件:
MotionEvent#getHistoricalX()MotionEvent#getHistoricalY()MotionEvent#getHistoricalPressure()MotionEvent#getHistoricalAxisValue()
防止手掌误触
使用触控笔绘图、手写或与您的应用互动时,用户有时会用手掌触摸屏幕。系统可能会在将其识别为手掌误触并予以忽略之前便将相应触摸事件(设置为 ACTION_DOWN 或 ACTION_POINTER_DOWN)报告给您的应用。
Android 通过分派 MotionEvent 来取消手掌触摸事件。如果您的应用收到 ACTION_CANCEL,则应取消相应手势。如果您的应用收到 ACTION_POINTER_UP,则应检查 FLAG_CANCELED 是否已设置。如果已设置,则应取消相应手势。
请勿仅检查 FLAG_CANCELED。在 Android 13(API 级别 33)及更高版本中,系统会为 ACTION_CANCEL 事件设置 FLAG_CANCELED,但在较低的 Android 版本中,系统不会设置该标志。
Android 12
在 Android 12(API 级别 32)及更低版本上,只能针对单指针触摸事件检测手掌误触。如果手掌触摸是唯一的指针,系统会通过在动作事件对象上设置 ACTION_CANCEL 来取消相应事件。如果还有其他指针,系统会设置 ACTION_POINTER_UP,这不足以检测手掌误触。
Android 13
在 Android 13(API 级别 33)及更高版本中,如果手掌触摸是唯一的指针,系统会对动作事件对象设置 ACTION_CANCEL 和 FLAG_CANCELED 以取消相应事件。如果还有其他指针,系统会设置 ACTION_POINTER_UP 和 FLAG_CANCELED。
每当您的应用收到带有 ACTION_POINTER_UP 的动作事件时,请检查 FLAG_CANCELED 以确定相应事件是否指示防止手掌误触(或其他事件取消)。
记事应用
ChromeOS 具有一个特殊的 intent,用于将已注册的记事应用呈现给用户。如需将应用注册为记事应用,请将以下内容添加到应用清单中:
<intent-filter>
<action android:name="org.chromium.arc.intent.action.CREATE_NOTE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
当应用向系统注册后,用户可以选择将其作为默认记事应用。当用户请求创建新记事时,应用应创建一个空白记事,以便用户使用触控笔输入内容。当用户希望为图片(例如屏幕截图或下载的图片)添加注释时,应用会启动并显示 ClipData,其中包含一个或多个具有 content:// URI 的内容。该应用应创建一条记事,这条记事将附加的第一张图片用作背景图片,该应用还应进入一种模式,用户可以在这种模式下使用触控笔在屏幕上进行绘制。
在没有触控笔的情况下测试记事 intent
如需测试在没有主动式触控笔的情况下应用是否可以正确响应记事 intent,请使用以下方法显示 ChromeOS 上的记事选项:
- 切换到开发模式并使设备可写入
- 按 Ctrl+Alt+F2 打开终端
- 运行命令
sudo vi /etc/chrome_dev.conf - 按
i进行修改,并在文件末尾的新行中添加--ash-enable-palette - 按 Esc 键进行保存,然后输入 :、w 和 q 并按 Enter 键
- 按 Ctrl + Alt + F1 键以返回 ChromeOS 常规界面
- 退出,然后重新登录
现在,任务栏上应该会显示触控笔菜单:
- 点按任务栏中的触控笔按钮,然后选择新建记事。这样应该会打开一条空白绘图记事。
- 截取屏幕截图。在任务栏中依次选择触控笔按钮 > 截取屏幕,或者下载图片。通知中应有为图片添加注释选项。这样应该会启动应用,并且图片已做好标注的准备。
鼠标和触控板支持
大多数应用通常只需处理三个以大屏设备为中心的事件:右键点击、悬停以及拖放。
右键点击
任何导致应用显示上下文菜单的操作(例如轻触并按住列表项)也应响应右键点击事件。
如需处理右键点击事件,应用应注册 View.OnContextClickListener:
Box(modifier = Modifier.fillMaxSize()) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
val rootView = FrameLayout(context)
val onContextClickListener =
View.OnContextClickListener { view ->
showContextMenu()
true
}
rootView.setOnContextClickListener(onContextClickListener)
rootView
},
)
}
如需详细了解如何构建上下文菜单,请参阅创建上下文菜单。
悬停
您可以通过处理悬停事件,让应用布局更精致、更易于使用。对于自定义组件,尤其如此:
最常见的两个示例如下:
- 通过更改鼠标指针图标,向用户指示元素是否具有互动行为,例如是否可点击或可编辑
- 当指针悬停在大型列表或网格中的项目上时,向这些项目添加视觉反馈
拖放
在多窗口环境中,用户希望能够在应用之间拖放项目。这适用于桌面设备以及处于分屏模式下的平板电脑、手机和可折叠设备。
考虑用户是否有可能将项目拖入您的应用。例如,照片编辑器应该能接收照片、音频播放器应该能接收音频文件、绘图程序应该能接收照片。
如需添加拖放支持,请参阅 拖放 ,并参阅 Android on ChromeOS — Implementing Drag & Drop(ChromeOS 上的 Android - 实现拖放)博文。
有关 ChromeOS 的特殊注意事项
- 请务必使用
requestDragAndDropPermissions()请求权限,然后才能访问从应用外部拖入的项目 内容必须具有
View.DRAG_FLAG_GLOBAL标志,才能拖动到其他应用请参阅启动拖动事件
高级指针支持
对鼠标和触控板输入进行高级处理的应用应实现
pointerInput 修饰符,以获取 PointerEvent:
@Composable private fun LogPointerEvents(filter: PointerEventType? = null) { var log by remember { mutableStateOf("") } Column { Text(log) Box( Modifier .size(100.dp) .background(Color.Red) .pointerInput(filter) { awaitPointerEventScope { while (true) { val event = awaitPointerEvent() // handle pointer event if (filter == null || event.type == filter) { log = "${event.type}, ${event.changes.first().position}" } } } } ) } }
检查 PointerEvent 对象以确定以下内容:
PointerType:来自PointerEvent#changes的鼠标、触控笔、触摸等PointerEventType:指针操作,例如按下、移动、滚动和释放
游戏控制器
部分大屏 Android 设备最多支持四个游戏控制器。使用标准 Android 游戏控制器 API 处理游戏控制器(请参阅支持游戏控制器)。
游戏控制器按钮按照通用映射映射到通用值。 但并非所有游戏控制器制造商都遵循相同的映射惯例。 如果您允许用户选择不同的常见控制器映射,就能提供更好的体验。如需了解详情,请参阅处理游戏手柄按钮按下操作。
输入转换模式
默认情况下,ChromeOS 会启用输入转换模式。对于大多数 Android 应用来说,此模式有助于应用在桌面环境中按预期运行。例如,自动启用触控板上的双指滚动、鼠标滚轮滚动,以及将原始显示坐标映射到窗口坐标。通常,应用开发者无需自行实现这些行为。
如果某个应用实现了自定义输入行为(例如,定义一种自定义触控板双指张合操作),或者这些输入转换未提供该应用预期的输入事件,您可以停用输入翻译模式,方法是将以下代码添加到 Android 清单:
<uses-feature
android:name="android.hardware.type.pc"
android:required="false" />