在大屏设备上,用户通常使用键盘、鼠标、触控板、手写笔或游戏手柄与应用互动。为了使您的应用能够接受来自外部设备的输入,请执行以下操作:
- 测试基本键盘支持,例如按 Ctrl+Z 撤消、按 Ctrl+C 复制以及按 Ctrl+S 保存。有关默认键盘快捷键的列表,请参阅处理键盘操作。
- 测试高级键盘支持,例如,Tab 键和方向键键盘导航,Enter 键文本输入确认,以及媒体应用中的 Spacebar 播放和暂停。
- 测试基本鼠标互动,包括右键点击显示上下文菜单、悬停时图标变化以及自定义组件上的鼠标滚轮或触控板滚动事件。
- 测试应用专用的输入设备,例如手写笔、游戏控制器和音乐应用 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") }
}
}
如需了解更多信息,请参阅使用焦点组提供一致的导航。
仅使用键盘测试对您应用中每个 UI 元素的访问。常用元素应无需鼠标或触摸输入即可访问。
请记住,键盘支持对于有无障碍需求的用户来说可能至关重要。
按键
对于由屏幕虚拟键盘 (IME) 处理的文本输入,例如用于 TextField
,应用在大屏设备上应按预期行为,无需额外开发工作。对于框架无法预期的按键,应用需要自行处理其行为。对于具有自定义视图的应用尤其如此。
一些示例包括:聊天应用使用 Enter 键发送消息,媒体应用使用 Spacebar 键启动和停止播放,以及游戏使用 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()
回调并为每个接收到的键码添加预期行为。
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
对象,请将 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.AXIS_TILT
和MotionEvent.AXIS_ORIENTATION
的MotionEvent#getAxisValue()
提供手写笔的物理倾斜和方向(如果支持)。
历史点
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>
当应用在系统中注册后,用户可以将其选为默认笔记应用。当请求新笔记时,应用应创建一个空笔记,准备好进行手写笔输入。当用户希望批注图片(例如屏幕截图或下载的图片)时,应用会启动并带有包含一个或多个 content://
URI 项的 ClipData
。应用应创建一个笔记,将第一张附加图片用作背景图片,并进入用户可以使用手写笔在屏幕上绘图的模式。
在没有手写笔的情况下测试笔记 intent
[待删除部分。]
要在没有激活手写笔的情况下测试应用是否正确响应笔记 intent,请使用以下方法在 ChromeOS 上显示笔记选项:
- 切换到开发者模式并使设备可写入
- 按 Ctrl+Alt+F2 打开终端
- 运行命令
sudo vi /etc/chrome_dev.conf
- 按
i
编辑并在文件末尾添加新行--ash-enable-palette
- 按 Esc 保存,然后输入 :、w、q,再按 Enter
- 按 Ctrl+Alt+F1 返回常规 ChromeOS UI
- 退出,然后重新登录
手写笔菜单现在应该在任务栏上
- 点击任务栏上的手写笔按钮,然后选择新笔记。这应该会打开一个空白绘图笔记。
- 截取屏幕截图。从任务栏中选择手写笔按钮 > 截取屏幕或下载图片。通知中应有批注图片的选项。这应该会启动应用,并准备好批注图片。
鼠标和触控板支持
大多数应用通常只需要处理三个以大屏为中心的事件:右键点击、悬停和拖放。
右键点击
任何导致应用显示上下文菜单的操作,例如在列表项上长按,都应对手动点击事件做出反应。
要处理右键点击事件,应用应注册 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
},
)
}
有关构建上下文菜单的详细信息,请参阅创建上下文菜单。
悬停
通过处理悬停事件,您可以让您的应用布局感觉更精致、更易于使用。对于自定义组件尤其如此:
最常见的两个例子是:
- 通过更改鼠标指针图标,向用户指示某个元素是否具有交互行为,例如可点击或可编辑。
- 当指针悬停在大型列表或网格中的项目上时,向其添加视觉反馈。
拖放
在多窗口环境中,用户希望能够在应用之间拖放项目。这适用于桌面设备以及分屏模式下的平板电脑、手机和折叠屏设备。
考虑用户是否可能将项目拖动到您的应用中。例如,照片编辑器应能接收照片,音频播放器应能接收音频文件,绘图程序应能接收照片。
要添加拖放支持,请参阅拖放,并查看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" />